Compare commits

..

239 Commits

Author SHA1 Message Date
github-actions[bot]
7ed9cfa018 chore: Bump the version in Cargo.lock 2024-12-21 23:48:07 +00:00
github-actions[bot]
71791afca0 bump: version 0.4.1 → 0.4.2 [skip ci] 2024-12-21 23:48:04 +00:00
a52eddfdac ci: Reverted failed release [skip ci] 2024-12-21 16:47:31 -07:00
0474978ac0 ci: Fixed final typo I hope [skip ci] 2024-12-21 16:46:16 -07:00
4b94a0ce2a ci: Fixed typo in release flow [skip ci] 2024-12-21 16:44:48 -07:00
0d1eac7610 ci: Final test of corrected release flow for GitHub [skip ci] 2024-12-21 16:43:04 -07:00
b129e5d5a4 Merge branch 'main' of github.com:Dark-Alex-17/managarr 2024-12-21 14:59:58 -07:00
9f5c22890d ci: Configure release workflow to only release docker now [skip ci] 2024-12-21 14:58:54 -07:00
github-actions[bot]
74f4a19003 chore: Bump the version in Cargo.lock 2024-12-21 21:57:10 +00:00
github-actions[bot]
717d9872dc bump: version 0.4.1 → 0.4.2 [skip ci] 2024-12-21 21:57:08 +00:00
439270fe2d ci: Revert failed release and add fix to (hopefully) finally fix the GitHub release [skip ci] 2024-12-21 14:37:18 -07:00
github-actions[bot]
601fd55435 chore: Bump the version in Cargo.lock 2024-12-21 21:34:35 +00:00
github-actions[bot]
341c5254f1 bump: version 0.4.1 → 0.4.2 [skip ci] 2024-12-21 21:34:33 +00:00
cb2701ada4 ci: Fixed a typo in the Managarr docker release. Still in release testing mode [skip ci] 2024-12-21 14:04:54 -07:00
28fcccce98 Reverted failed release once again... 2024-12-21 13:51:01 -07:00
2ba3e56772 docs: Removed unnecessary revert commit mention in the CHANGELOG 2024-12-19 21:51:02 -07:00
github-actions[bot]
f3fa3401f1 chore: Bump the version in Cargo.lock 2024-12-20 04:19:08 +00:00
github-actions[bot]
820f339982 bump: version 0.4.1 → 0.4.2 [skip ci] 2024-12-20 04:19:04 +00:00
afd9dd34a7 fix: Revert failed release [skip ci] 2024-12-19 20:37:32 -07:00
4a4e5d2cf4 Merge remote-tracking branch 'refs/remotes/origin/main' 2024-12-19 20:34:54 -07:00
029b00532e ci: Fix a typo in the download-artifacts version [skip ci] 2024-12-19 20:34:41 -07:00
github-actions[bot]
0b052684c2 chore: Bump the version in Cargo.lock 2024-12-20 03:33:06 +00:00
github-actions[bot]
3dda80b50f bump: version 0.4.1 → 0.4.2 [skip ci] 2024-12-20 03:33:03 +00:00
9dede1e45d ci: Removed the now defunct Scoop testing workflow [skip ci] 2024-12-19 20:13:04 -07:00
6dc64c6edf docs: Updated README with Mac and Linux installation steps with Homebrew [skip ci] 2024-12-19 19:56:27 -07:00
c21748df1e ci: Deleted the test homebrew release workflow and updated the main release workflow to have the homebrew release [skip ci] 2024-12-19 19:52:38 -07:00
27d024cca2 ci: Set username and email globally [skip ci] 2024-12-19 19:48:19 -07:00
b0dd2575d9 ci: Typo in clone URL [skip ci] 2024-12-19 19:47:30 -07:00
7a004c2cfc ci: Test using the token directly in the clone [skip ci] 2024-12-19 19:46:32 -07:00
53ab164379 ci: Changed username [skip ci] 2024-12-19 19:45:00 -07:00
bf4fb3a55d ci: Fixed a typo in the homebrew test [skip ci] 2024-12-19 19:43:39 -07:00
2235f1d0d9 ci: Created a token for all Managarr repos [skip ci] 2024-12-19 19:42:13 -07:00
17dcb2b10b ci: Removed the use of the deploy key to force git to use SSH [skip ci] 2024-12-19 19:26:13 -07:00
4c7c62eb0b ci: Changed to cloning the homebrew repo with SSH [skip ci] 2024-12-19 19:25:09 -07:00
81dcf4b003 ci: Updated the username and email to be that of my personal user [skip ci] 2024-12-19 19:22:45 -07:00
3fffdd20a8 ci: Set username and email globally [skip ci] 2024-12-19 19:20:37 -07:00
078d75473b ci: Update the test homebrew release [skip ci] 2024-12-19 19:18:35 -07:00
8b7ff58c7d ci: Created a test homebrew release workflow [skip ci] 2024-12-19 19:15:59 -07:00
ff67eebcea ci: Removed scoop workflow [skip ci] 2024-12-19 18:25:05 -07:00
github-actions[bot]
e585eb46ea chore: Update Scoop bucket for version 0.4.1 [skip ci] 2024-12-20 01:10:06 +00:00
01507f88da ci: Corrected the use of the SSH key for pushing to the repo [skip ci] 2024-12-19 18:08:43 -07:00
6344b6dba2 ci: Fixing the push step [skip ci] 2024-12-19 18:04:07 -07:00
d3042cf724 ci: Corrected the packager script path [skip ci] 2024-12-19 17:58:00 -07:00
fd4b9a4f15 ci: Updated a typo in the scoop test workflow config [skip ci] 2024-12-19 17:55:58 -07:00
2879a2aa67 ci: Corrected the scoop workflow to publish changes to repo first, and then to publish them to Scoop [skip ci] 2024-12-19 17:53:16 -07:00
5cd8cb66f1 ci: Fix erroring out when removing the scoop bucket if files don't exist [skip ci] 2024-12-19 17:40:11 -07:00
47c206ca1d ci: Created the test scoop deployment workflow [skip ci] 2024-12-19 17:34:47 -07:00
8f22b64a0a ci: Moved the package.py script into the main deployment directory for reuse, and also updated the Chocolatey workflow to push the generated Chocolatey files to the repo for posterity [skip ci] 2024-12-19 17:23:27 -07:00
7941b3d16c docs: Updated the README to include steps on how to install Managarr in Windows using Chocolatey [skip ci] 2024-12-19 16:55:06 -07:00
e9ded4bde4 ci: Delete the test chocolatey deploy and add the official chocolatey deploy to the release workflow [skip ci] 2024-12-19 16:50:58 -07:00
7b02472f67 ci: Fix the test workflow to use the first release that actually has a sha256 for Windows attached to it [skip ci] 2024-12-19 16:46:16 -07:00
a370d67121 ci: Attempting to explicitly set the output encoding to UTF-8 for Windows [skip ci] 2024-12-19 16:41:56 -07:00
4f6e64083a ci: Explicitly set the encoding as UTF-8 in the python script since that is not the windows default encoding [skip ci] 2024-12-19 16:36:56 -07:00
276a672df4 ci: Try refreshing the environment after installing managarr choco from local [skip ci] 2024-12-19 16:31:44 -07:00
e55a157977 ci: Fixed a typo in the chocolatey deployment PowerShell template [skip ci] 2024-12-19 16:26:28 -07:00
e1e91d1add ci: Testing fixes in the test chocolatey deployment workflow [skip ci] 2024-12-19 16:25:04 -07:00
8102b5933f ci: Mock the artifact creation for the test workflow [skip ci] 2024-12-19 16:19:43 -07:00
bc60128679 ci: Fixed a typo in the test chocolatey deployment workflow [skip ci] 2024-12-19 16:11:41 -07:00
fc3a7ab789 ci: Created test GHA workflow to rest releasing Managarr to Chocolatey [skip ci] 2024-12-19 16:10:38 -07:00
93d3e6cec7 docs: Fixed working in the README about the new managarr-demo.alexjclarke.com site 2024-12-18 18:22:42 -07:00
95779f1ac2 docs: Updated the README to point to the new managarr-demo.alexjclarke.com site for testing out the TUI as an alternative to running the command 2024-12-18 18:20:36 -07:00
7bc57d7696 ci: Remove the test multi-platform docker test job [skip ci] 2024-12-18 18:12:38 -07:00
7e7b75f378 ci: Correct another typo in the test multi-platform job [skip ci] 2024-12-18 17:57:31 -07:00
a958350b2d ci: Fix typo in multi-platform release test [skip ci] 2024-12-18 17:56:41 -07:00
c502b3d08f ci: Attempting multi-platform builds for docker [skip ci] 2024-12-18 17:54:37 -07:00
Alex Clarke
e602b66188 Merge pull request #27 from Dark-Alex-17/race-condition-refactor
Race condition refactor
2024-12-18 17:38:13 -07:00
12fba15bcf style: Clean up all remaining unused test helper functions 2024-12-18 01:44:27 -07:00
7e36ad4e8a fix(sonarr): Pass the series ID alongside all UpdateAndScan events when publishing to the networking channel 2024-12-18 01:40:47 -07:00
33249f509f fix(sonarr): pass the series ID alongside all TriggerAutomaticSeriesSearch events when publishing to the networking channel 2024-12-18 01:38:05 -07:00
ed645dd0d5 fix(sonarr): Pass the series ID and season number alongside all TriggerAutomaticSeasonSearch events when publishing to the networking channel 2024-12-18 01:34:45 -07:00
b12c635c27 fix(sonarr): Pass the episode ID alongside all TriggerAutomaticEpisodeSearch events when publishing to the networking channel 2024-12-18 01:29:30 -07:00
c16ecfb188 fix(sonarr): Pass the episode ID alongside all ToggleEpisodeMonitoring events when publishing to the networking channel 2024-12-18 01:22:28 -07:00
18a8b81631 fix(sonarr): Pass the series ID and season number alongside all toggle season monitoring events when publishing to the networking channel 2024-12-18 01:12:32 -07:00
1d404d4d2c fix(sonarr): Pass the indexer ID directly alongside all TestIndexer events when publishing to the networking channel 2024-12-18 01:01:01 -07:00
42479ced21 fix(sonarr): Provide the task name directly alongside all StartTask events when publishing to the networking channel 2024-12-18 00:56:21 -07:00
1193b8c848 fix(sonarr): Pass the search query directly to the networking channel when searching for a new series 2024-12-18 00:49:36 -07:00
ec8d748991 fix(sonarr): Pass the series ID alongside all GetSeriesHistory events when publishing to the networking channel 2024-12-18 00:39:50 -07:00
bafaf7ca7a fix(sonarr): Pass the series ID alongside all GetSeriesDetails events when publishing to the networking channel 2024-12-18 00:37:06 -07:00
f7315a3bec fix(sonarr): Pass series ID and season number alongside all ManualSeasonSearch events when publishing to the networking channel 2024-12-18 00:32:36 -07:00
f655ca989d fix(sonarr): Provide the series ID and season number alongside all GetSeasonHistory events when publishing to the networking channel 2024-12-18 00:22:24 -07:00
fcb87a6779 fix(sonarr): Pass the episode ID alongside all ManualEpisodeSearch events when publishing to the networking channel 2024-12-18 00:12:18 -07:00
924f8d5eff fix(sonarr): Pass events alongside all GetLogs events when publishing to the networking channel 2024-12-18 00:07:59 -07:00
64ecc38073 fix(sonarr): Pass the episode ID alongside all GetEpisodeHistory events when publishing to the networking channel 2024-12-18 00:05:22 -07:00
5f94dbcabe fix(sonarr): Pass series ID alongside all GetEpisodeFiles events when publishing to the networking channel 2024-12-17 23:59:49 -07:00
2ecc591966 fix(sonarr): Pass series ID alognside all GetEpisodes events when publishing to the networking channel 2024-12-17 23:57:13 -07:00
30ba1f3317 fix(sonarr): Pass the episode ID alongside all GetEpisodeDetails events when publishing to the networking channel 2024-12-17 23:52:18 -07:00
4fdf9b3df1 fix(sonarr): Pass history events alongside all GetHistory events when publishing to the networking channel 2024-12-17 23:40:23 -07:00
22fe1a8f73 fix(sonarr): Construct and pass edit series parameters alongside all EditSeries events when publishing to the networking channel 2024-12-17 23:37:18 -07:00
38c0ad29dd fix(sonarr): Construct and pass edit indexer parameters alongside all EditIndexer events when publishing to the networking channel 2024-12-17 23:22:56 -07:00
89d106c03e fix(sonarr): Construct and pass edit all indexer settings alongside all EditAllIndexerSettings events when publishing to the networking channel 2024-12-17 23:05:29 -07:00
3e36bcf307 fix(sonarr): Construct and pass delete series params alongside all DeleteSeries events when publishing to the networking channel 2024-12-17 22:56:14 -07:00
acf983c07c fix(sonarr): Corrected a bug that would cause a crash if a user spams the ESC key while searching for a new series and the search results are still loading 2024-12-17 22:45:34 -07:00
fedb78fb88 fix(sonarr): Pass the root folder ID alongside all DeleteRootFolder events when publishing to the networking channel 2024-12-17 22:42:37 -07:00
db64a0968b fix(sonarr): Pass the indexer ID alongside all DeleteIndexer events when publishing to the networking channel 2024-12-17 22:37:50 -07:00
aece20af47 fix(sonarr): Pass the episode file ID alongside all DeleteEpisodeFile events when publishing to the networking channel 2024-12-17 22:33:10 -07:00
6c5a73f78f fix(sonarr): Pass the download ID alongside all DeleteDownload events published to the networking channel 2024-12-17 22:27:08 -07:00
906e093152 fix(sonarr): Pass the blocklist item ID alongside the DeleteBlocklistItem event when publishing to the networking channel 2024-12-17 22:22:32 -07:00
478b4ae3c0 fix(sonarr): Construct and pass the add series body alongside AddSeries events when publishing to the networking channel 2024-12-17 22:16:43 -07:00
23971cbb76 fix(sonarr): Construct and pass the AddRootFolderBody alongside all AddRootFolder events when publishing to the networking channel 2024-12-17 21:48:52 -07:00
43410fac60 fix(radarr): Pass the movie ID alongside all UpdateAndScan events published to the networking channel 2024-12-17 21:34:14 -07:00
cb8035a2ce fix(radarr): Provide the movie ID alongside all TriggerAutomaticMovieSearch events when publishing to the networking channel 2024-12-17 21:26:34 -07:00
8d071c7674 fix(radarr): Pass in the indexer id with all TestIndexer events when publishing to the networking channel 2024-12-17 21:21:23 -07:00
965c488468 fix(radarr): Pass in the task name alongside the StartTask event when publishing to the networking channel 2024-12-17 21:13:47 -07:00
ede7f64c4b fix(radarr): Pass in the search query for the SearchNewMovie event when publishing to the networking channel 2024-12-17 21:06:07 -07:00
ba38dcdc15 fix(radarr): Pass in the movie ID alongside the GetReleases event when publishing to the networking channel 2024-12-17 20:52:31 -07:00
92d9222b05 fix(radarr): Pass in the movie ID alongside the GetMovieHistory event when publishing to the networking channel 2024-12-17 20:50:00 -07:00
4c396c3442 fix(radarr): Pass the movie ID in alongside the GetMovieDetaisl event when publishing to the networking channel 2024-12-17 20:47:29 -07:00
e1d5139e36 fix(radarr): Provide the movie id alongside the GetMovieCredits event when publishing to the networking channel 2024-12-17 20:42:52 -07:00
1ad35652f8 fix(radarr): Pass the number of log events to fetch in with the GetLogs event when publishing to the networking channel 2024-12-17 20:33:39 -07:00
9a9b13d604 fix(radarr): Construct and pass the edit movie parameters alongside the EditMovie event when publishing to the networking channel 2024-12-17 17:50:07 -07:00
77b8b61079 fix(radarr): Construct and pass params when publishing the EditIndexer event to the networking channel 2024-12-17 17:29:21 -07:00
bdf48d1bf4 fix(radarr): Construct and pass edit collection parameters alongside the EditCollection event when publishing to the networking channel 2024-12-17 16:32:35 -07:00
f8792ea012 fix(radarr): Build and pass the edit indexer settings body with the EditAllIndexerSettings event when publishing to the networking channel 2024-12-17 16:10:11 -07:00
4afde8b750 fix(radarr): Send the parameters alongside the DownloadRelease event when publishing to the networking channel 2024-12-17 15:56:58 -07:00
f5614995c7 fix(radarr): Pass the root folder ID in with the DeleteRootFolder event when publishing to the networking channel 2024-12-17 15:41:28 -07:00
9ea6dbec20 fix: Pass the delete movie params in with the DeleteMovie event when publishing to the networking channel 2024-12-17 15:35:29 -07:00
d73dfb9fc7 fix: Pass the indexer ID in with the DeleteIndexer event when sending to the networking channel 2024-12-17 15:21:34 -07:00
a7da73300c fix: Pass the download ID directly in the DeleteDownload event when publishing into the networking channel 2024-12-17 15:14:17 -07:00
a308b8fe95 fix: Blocklist Item ID passed in the DeleteBlocklistItem event when sent to the networking channel 2024-12-17 15:03:06 -07:00
1d1e42aeb1 fix: AddRootFolderBody now constructed prior to AddRootFolder event being sent down the network channel 2024-12-17 14:53:40 -07:00
1f81061152 Merge remote-tracking branch 'origin/main' into race-condition-refactor 2024-12-17 14:37:46 -07:00
368d5d3db7 fix: Cancel all requests when switching Servarr tabs to both improve performance and fix issue #15 2024-12-17 14:36:49 -07:00
0612a02d68 fix(add_movie_handler_tests): Added in a forgotten test for the build_add_movie_body function 2024-12-17 14:19:12 -07:00
a0d470087b Merge remote-tracking branch 'origin/main' into race-condition-refactor 2024-12-17 14:11:12 -07:00
3ecaf04eb4 fix: Missing tagged version of docker builds in release flow 2024-12-17 12:40:13 -07:00
df0811985d Fixed a bug in the release flow that published the docker image before the version bump 2024-12-17 12:15:36 -07:00
057ff0fef1 Fixed a bug in the release pipeline that created a conflict between the tag and the actual code 2024-12-16 21:09:24 -07:00
14c46f88ab fix: AddMovie Radarr event is now populated in the dispatch thread before being sent to the network thread 2024-12-16 15:31:26 -07:00
e38e430c77 fix: dynamically load servarrs in UI based on what configs are provided 2024-12-16 14:16:01 -07:00
Alex Clarke
93cd235aef docs: Update README.md to reference the Wekan board and not mention tracking the Beta release since it's live now 2024-12-14 02:26:38 -07:00
github-actions[bot]
df9bba32cb chore: Bump the version in Cargo.lock 2024-12-14 08:07:55 +00:00
github-actions[bot]
28a8f9b2fa bump: version 0.4.0 → 0.4.1 [skip ci] 2024-12-14 08:07:52 +00:00
e49b366d77 docs: Removed the docker version from the README [skip ci] 2024-12-14 00:46:35 -07:00
9a0963ca2c Merge remote-tracking branch 'origin/main' 2024-12-14 00:41:45 -07:00
17737a06a4 ci: Add the tar.gz files to the artifacts [skip ci] 2024-12-14 00:41:26 -07:00
github-actions[bot]
464779cc17 chore: Bump the version in Cargo.lock 2024-12-14 07:35:49 +00:00
github-actions[bot]
f25c0889a3 bump: version 0.3.7 → 0.4.0 [skip ci] 2024-12-14 07:35:46 +00:00
90170cb3d5 ci: Fixed a typo in the github-release job [skip ci] 2024-12-14 00:14:52 -07:00
4dcb141f3a ci: Fixed a typo in the docker release [skip ci] 2024-12-13 23:41:01 -07:00
133721917f ci: Attempting a different artifact job version to see if it corrects the error [skip ci] 2024-12-13 23:19:07 -07:00
766e23d265 ci: Use the same version of upload/download-artifact action [skip ci] 2024-12-13 23:06:36 -07:00
77d8e84e14 ci: Correct the artifact paths for the release [skip ci] 2024-12-13 22:42:42 -07:00
00c1cca412 ci: Fix the artifacts directory creation for Windows binaries [skip ci] 2024-12-13 22:24:03 -07:00
4968833d05 ci: Attempting a different way of creating the artifacts directory [skip ci] 2024-12-13 22:05:22 -07:00
d172fa17f6 ci: Attempting to fix the artifacts directory [skip ci] 2024-12-13 21:58:30 -07:00
3c99b38db7 ci: Support for arm64 docker builds 2024-12-13 21:40:53 -07:00
Alex Clarke
1128937cac Merge pull request #21 from Dark-Alex-17/sonarr-tui-support
Sonarr TUI support
2024-12-13 21:09:53 -07:00
6fc1228173 docs: Updated the README to include OS-specific steps for running Managarr on different platforms 2024-12-13 21:04:10 -07:00
b48a2efb7d fix(blocklist_handler): Fixed a breaking change between Sonarr v3 and v4 2024-12-13 20:48:10 -07:00
412cb2408e fix(style): Addressed linter complaints on formatting 2024-12-13 19:48:22 -07:00
682bc91855 fix: Implemented a handful of fixes that are breaking changes between Sonarr v3 and v4 2024-12-13 19:44:10 -07:00
f03120e5a1 ci: Updated CI to cross compile for a handful of additional architectures to increase availability 2024-12-13 17:58:59 -07:00
8dd63b30e8 feat(docs): Updated the README with new screeshots for the Sonarr release 2024-12-13 16:28:42 -07:00
54006c378f feat(handler): Support for toggling the monitoring status of a specified episode in the Sonarr UI 2024-12-13 16:18:02 -07:00
9269b66aa8 feat(handlers): Support for toggling the monitoring status of a season in the Sonarr UI 2024-12-13 16:10:06 -07:00
cfac433861 feat(keybindings): Added a new keybinding for toggling the monitoring of a highlighted table item 2024-12-13 14:53:39 -07:00
a28f8c3dd2 feat(cli): Support for toggling monitoring on a specific episode in Sonarr 2024-12-13 14:49:00 -07:00
4001dee1bd refactor(network): Changed the toggle episode monitoring handler to simply return empty since the response is always empty from Sonarr 2024-12-13 14:45:06 -07:00
d1ffd0d77f feat(network): Support for toggling the monitoring status of an episode in Sonarr 2024-12-13 14:40:11 -07:00
91ad50350d feat(cli): Support for toggling monitoring for a specific season in Sonarr 2024-12-13 14:09:11 -07:00
a88d43807e feat(network): Support for toggling monitoring/unmonitoring a season 2024-12-13 13:59:02 -07:00
98619664cf refactor(ui): Tweaked some of the color schemes in the series table 2024-12-13 13:10:57 -07:00
39f8ad2106 refactor: Fixed a couple of typos in some test function names 2024-12-13 11:51:23 -07:00
82ce38d7b5 feat(handlers): Support for the episode details popup 2024-12-12 18:52:27 -07:00
12eb453fc7 feat(ui): Support for the episode details UI 2024-12-12 16:25:02 -07:00
a84324d3bc feat(handler): Full handler support for the Season details UI in Sonarr 2024-12-11 23:18:37 -07:00
ed2211586e refactor(handlers): Refactored the handlers to all use the handle_table_events macro when appropriate and created tests for the macro so tests don't have to be duplicated across each handler 2024-12-11 17:03:52 -07:00
c09950d0af refactor(ui): Simplified the popup delegation so all future UI is easier to implement 2024-12-11 15:08:52 -07:00
e9a30382a3 feat(ui): Sonarr support for viewing season details 2024-12-10 18:23:09 -07:00
7bf3311102 feat(cli): Sonarr support for fetching a list of all episode files for a given series ID 2024-12-10 16:32:35 -07:00
cbad40245f feat(app): Dispatch support for Season Details to fetch both the current downloads as well as the episode files to match qualities to them 2024-12-10 16:23:30 -07:00
75c4fcbb9e feat(network): Support for fetching all episode files for a given series 2024-12-10 16:22:02 -07:00
f3b7f155b7 feat(app): Model and modal support for the season and episode details popups 2024-12-09 15:15:09 -07:00
6427a80bd1 feat(cli): Sonarr support for fetching season history events 2024-12-09 14:30:07 -07:00
5b65e87225 feat(network): Sonarr support for fetching season history 2024-12-09 14:15:47 -07:00
1b8b19fde5 refactor(indexers_handler): Use the new handle_table_events macro 2024-12-08 14:42:18 -07:00
03d7aed258 refactor(root_folders_handler): Use the new handle_table_events macro 2024-12-08 14:38:26 -07:00
23d149093f refactor(blocklist_handler): Use the new handle_table_events macro 2024-12-08 14:34:47 -07:00
27f12716d9 refactor(downloads_handler): Use the new handle_table_events macro 2024-12-08 14:28:12 -07:00
048877bbb6 refactor(collection_details_handler): use the new handle_table_events macro 2024-12-08 14:22:59 -07:00
87a652d911 refactor(collections_handler): Use the new handle_table_events macro 2024-12-08 14:14:24 -07:00
d6863dc1fd refactor(movie_details_handler): Use the new handle_table_events macro 2024-12-08 14:04:34 -07:00
f1d934b0a6 refactor(library_handler): Radarr use the new handle_table_events macro 2024-12-08 13:43:01 -07:00
5850f7a621 refactor(indexers_handler): Use the new handle_table_events macro 2024-12-08 13:26:59 -07:00
dd23e84ccf refactor(indexers_handler): Use the new handle_table_events macro 2024-12-08 13:24:18 -07:00
b060518778 refactor(root_folder_handler): Use the new handle_table_events macro 2024-12-08 13:15:59 -07:00
de95f13feb fix(handler_tests): Fixed all delegation tests to have initial conditions set properly 2024-12-08 13:10:17 -07:00
0205f13e53 refactor(history_handler): Use the new handle_table_event macro 2024-12-08 13:08:43 -07:00
b4de97dfe2 refactor(blocklist_handler): Use the new handle_table_events macro 2024-12-08 12:39:07 -07:00
35bc6cf31c refactor(downloads_handler): Use the new handle_table_events macro 2024-12-08 12:35:12 -07:00
c58e8b1a00 refactor(series_details_handler): Use the new handle_table_events macro 2024-12-08 12:29:59 -07:00
accdf99503 fix(ui): Fixed a bug that requires a minimum height for all popups so all error messages and other simple popups appear 2024-12-07 19:36:43 -07:00
47b609369b refactor(handler): Created a macro to handle all table key events to reduce code duplication and make future implementations faster; Only refactored the Sonarr library to use it thus far 2024-12-07 19:20:13 -07:00
23b1ca4371 feat(ui): Sonarr support for the series details popup 2024-12-06 20:30:26 -07:00
73d666d1f5 feat(ui): Sonarr support for editing a series from within the series details popup 2024-12-05 19:11:54 -07:00
b27c13cf74 fix(handler): Fixed a bug in the history handler that wouldn't reset the filter or search if a user hit 'esc' on the History tab 2024-12-05 19:08:11 -07:00
bd1a4f0939 feat(ui): Sonarr Series details UI is now available 2024-12-05 19:07:45 -07:00
5abed23cf2 refactor(ui): all table search and filter functionality is now available directly through the ManagarrTable widget to make life easier moving forward 2024-12-05 19:07:03 -07:00
9d0948e124 refactor(keys): Created a auto search key instead of reusing the existing search key to make things easier 2024-12-05 12:29:09 -07:00
678bc77a23 fix(ui): Fix the System Details Tasks popup to be navigable in both Sonarr and Radarr 2024-12-05 11:45:46 -07:00
00cdeee5c6 feat(ui): Full Sonarr system tab support
Signed-off-by: Alex Clarke <alex.j.tusa@gmail.com>
2024-12-04 17:41:30 -07:00
2d251554ad feat(handler): System handler support for Sonarr 2024-12-04 17:04:36 -07:00
1b5d70ae2d perf: Improved performance by optimizing API calls to only refresh when the tick prompts a refresh. All UI is now significantly faster 2024-12-04 16:46:06 -07:00
2d2901f6dc feat(ui): Full Sonarr support for the indexer tab 2024-12-04 16:39:37 -07:00
a0b27ec105 feat(ui): Support for modifying the indexer priority in Radarr 2024-12-03 18:12:23 -07:00
093ef136e7 feat(handler): Full indexer tab handler support 2024-12-03 17:46:37 -07:00
8660de530d feat(ui): Root folder tab support 2024-12-03 16:24:23 -07:00
bda6f253e0 feat(handlers): Support for root folder actions 2024-12-03 16:18:39 -07:00
4eb974567f feat(ui): History tab support 2024-12-02 18:47:50 -07:00
4f5bad5874 feat(handler): History tab support 2024-12-02 18:03:59 -07:00
1c6e798632 feat(ui): Blocklist UI support 2024-12-02 16:54:27 -07:00
3186fb42e7 feat(handler): Wired in the blocklist handler to the main handlers 2024-12-02 16:39:40 -07:00
4b7185fbb0 feat(handler): Blocklist handler support 2024-12-02 16:37:46 -07:00
f0d8555a8a feat(ui): Downloads tab support 2024-12-02 15:57:48 -07:00
f338dfcb12 feat(handler): Download tab support 2024-12-02 15:40:11 -07:00
188d781b0d feat(ui): Edit series support 2024-12-02 15:31:12 -07:00
adb1f07fd0 feat(handler): Edit series support 2024-12-02 14:58:51 -07:00
82e51be096 feat(ui): Add series support Sonarr 2024-12-02 13:53:28 -07:00
d7f6d12f59 feat(handler): Add series support for Sonarr 2024-12-02 12:43:17 -07:00
0db57fbff1 feat(ui): Delete a series 2024-12-02 11:45:13 -07:00
b1bdc19afb feat(handler): Support for deleting a series in Sonarr 2024-12-02 11:30:34 -07:00
b75a95a708 feat(ui): Support for the Series table 2024-12-01 14:08:06 -07:00
c3fb5dcd5f feat(handlers): Sonarr key support for the Series table 2024-12-01 13:48:48 -07:00
21911f93d1 feat(models): Added the necessary contextual help and tabs for the Sonarr UI 2024-12-01 12:05:20 -07:00
f7c96d81e9 refactor(BlockSelectionState): Refactored so selection of blocks in 2x2 grids is more intuitive and added left() and right() methods to aid this effort. 2024-11-30 12:22:46 -07:00
9b2040059d fix(ui): Fixed a potential rare bug in the UI where the application would panic if the height of the downloads window is 0. 2024-11-29 16:31:51 -07:00
08f190fc6e feat(ui): Initial UI support for switching to Sonarr tabs 2024-11-29 15:58:19 -07:00
4d1b0fe301 docs(context): Updated the Servarr context clues to say how to switch Servarr tabs via TAB and SHIFT+TAB 2024-11-27 17:14:40 -07:00
f139db07d9 feat(app): Dispatch support for all relevant Sonarr blocks 2024-11-27 17:06:20 -07:00
Alex Clarke
73a4129000 Update environment variables table so it appears better in Crates.io [skip ci] 2024-11-25 20:25:04 -07:00
1ddf797e28 ci: Updated the release so all GitHub release names are correctly simply the version: v0.1.2 [skip ci] 2024-11-25 20:11:15 -07:00
github-actions[bot]
18280f0478 chore: Bump the version in Cargo.lock 2024-11-26 03:04:19 +00:00
github-actions[bot]
4348705a0a bump: version 0.3.6 → 0.3.7 [skip ci] 2024-11-26 03:04:17 +00:00
5a1b92547d fix(ci): Forgot to also pull in the most recent changes [skip ci] 2024-11-25 20:02:37 -07:00
github-actions[bot]
9e44713985 chore: Bump the version in Cargo.lock 2024-11-26 02:59:13 +00:00
github-actions[bot]
e93837fef7 bump: version 0.3.5 → 0.3.6 [skip ci] 2024-11-26 02:59:11 +00:00
6006c9d0e8 fix(ci): Ensure the Release Crate job fetches the most recent commit before publishing the crate [skip ci] 2024-11-25 19:58:34 -07:00
github-actions[bot]
c93543186a chore: Bump the version in Cargo.lock 2024-11-26 02:49:11 +00:00
github-actions[bot]
e1b74d7a36 bump: version 0.3.3 → 0.3.4 [skip ci] 2024-11-26 02:49:07 +00:00
262 changed files with 41633 additions and 16283 deletions
+3
View File
@@ -4,6 +4,9 @@ set -e
echo "Running pre-push hook:" echo "Running pre-push hook:"
echo "Executing: cargo fmt"
cargo fmt
echo "Executing: make lint" echo "Executing: make lint"
make lint make lint
+3
View File
@@ -4,6 +4,9 @@ set -e
echo "Running pre-push hook:" echo "Running pre-push hook:"
echo "Executing: cargo fmt --check"
cargo fmt --check
echo "Executing: make lint" echo "Executing: make lint"
make lint make lint
+11 -2
View File
@@ -11,8 +11,6 @@ name: Check
env: env:
CARGO_TERM_COLOR: always CARGO_TERM_COLOR: always
# ensure that the workflow is only triggered once per PR, subsequent pushes to the PR will cancel
# and restart the workflow. See https://docs.github.com/en/actions/using-jobs/using-concurrency
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true cancel-in-progress: true
@@ -24,14 +22,18 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Install Rust stable - name: Install Rust stable
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@stable
with: with:
components: rustfmt components: rustfmt
- name: Run cargo fmt - name: Run cargo fmt
run: cargo fmt -- --check run: cargo fmt -- --check
- name: Cache Cargo dependencies - name: Cache Cargo dependencies
uses: Swatinem/rust-cache@v2 uses: Swatinem/rust-cache@v2
clippy: clippy:
name: ${{ matrix.toolchain }} / clippy name: ${{ matrix.toolchain }} / clippy
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -45,12 +47,15 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Install Rust stable - name: Install Rust stable
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@stable
with: with:
components: clippy components: clippy
- name: Run clippy action - name: Run clippy action
uses: clechasseur/rs-clippy-check@v3 uses: clechasseur/rs-clippy-check@v3
- name: Cache Cargo dependencies - name: Cache Cargo dependencies
uses: Swatinem/rust-cache@v2 uses: Swatinem/rust-cache@v2
doc: doc:
@@ -61,8 +66,10 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Install Rust nightly - name: Install Rust nightly
uses: dtolnay/rust-toolchain@nightly uses: dtolnay/rust-toolchain@nightly
- name: Run cargo doc - name: Run cargo doc
run: cargo doc --no-deps --all-features run: cargo doc --no-deps --all-features
env: env:
@@ -73,9 +80,11 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Install 1.82.0 - name: Install 1.82.0
uses: dtolnay/rust-toolchain@master uses: dtolnay/rust-toolchain@master
with: with:
toolchain: 1.82.0 toolchain: 1.82.0
- name: cargo +1.82.0 check - name: cargo +1.82.0 check
run: cargo check run: cargo check
+397 -24
View File
@@ -18,7 +18,8 @@ on:
- major - major
jobs: jobs:
bump: bump-version:
name: bump-version
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Configure SSH for Git - name: Configure SSH for Git
@@ -70,9 +71,11 @@ jobs:
- name: Get the new version tag - name: Get the new version tag
id: version id: version
run: | run: |
mkdir -p artifacts
NEW_TAG=$(cz version --project) NEW_TAG=$(cz version --project)
echo "New version: $NEW_TAG" echo "New version: $NEW_TAG"
echo "version=$NEW_TAG" >> $GITHUB_ENV echo "version=$NEW_TAG" >> $GITHUB_ENV
echo "$NEW_TAG" > artifacts/release-version
- name: Get the previous version tag - name: Get the previous version tag
id: prev_version id: prev_version
@@ -85,19 +88,8 @@ jobs:
id: changelog id: changelog
run: | run: |
changelog=$(conventional-changelog -p angular -i CHANGELOG.md -s --from ${{ env.prev_version }} --to ${{ env.version }}) changelog=$(conventional-changelog -p angular -i CHANGELOG.md -s --from ${{ env.prev_version }} --to ${{ env.version }})
echo "$changelog" > changelog.md echo "$changelog" > artifacts/changelog.md
echo "changelog_body=$(cat changelog.md)" >> $GITHUB_ENV echo "changelog_body=$(cat artifacts/changelog.md)" >> $GITHUB_ENV
- name: Create a GitHub Release
uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: v${{ env.version }}
name: "Release v${{ env.version }}"
body: ${{ env.changelog_body }}
draft: false
prerelease: false
- name: Push changes - name: Push changes
env: env:
@@ -105,10 +97,37 @@ jobs:
run: | run: |
git push origin --follow-tags git push origin --follow-tags
release-crate: - name: Upload artifacts
needs: bump uses: actions/upload-artifact@v3
name: Release Crate with:
runs-on: ubuntu-latest name: artifacts
path: artifacts
build-release-artifacts:
name: build-release
needs: [bump-version]
runs-on: ${{ matrix.job.os }}
env:
RUST_BACKTRACE: 1
strategy:
fail-fast: true
matrix:
# prettier-ignore
job:
- { name: "macOS-arm64", os: "macOS-latest", target: "aarch64-apple-darwin", artifact_suffix: "macos-arm64", use-cross: true }
- { name: "macOS-amd64", os: "macOS-latest", target: "x86_64-apple-darwin", artifact_suffix: "macos" }
- { name: "windows-amd64", os: "windows-latest", target: "x86_64-pc-windows-msvc", artifact_suffix: "windows" }
- { name: "windows-aarch64", os: "windows-latest", target: "aarch64-pc-windows-msvc", artifact_suffix: "windows-aarch64", use-cross: true }
- { name: "linux-gnu", os: "ubuntu-latest", target: "x86_64-unknown-linux-gnu", artifact_suffix: "linux" }
- { name: "linux-musl", os: "ubuntu-latest", target: "x86_64-unknown-linux-musl", artifact_suffix: "linux-musl", use-cross: true, }
- { name: "linux-aarch64-gnu", os: "ubuntu-latest", target: "aarch64-unknown-linux-gnu", artifact_suffix: "aarch64-gnu", use-cross: true, test-bin: "--bin managarr" }
- { name: "linux-aarch64-musl", os: "ubuntu-latest", target: "aarch64-unknown-linux-musl", artifact_suffix: "aarch64-musl", use-cross: true, test-bin: "--bin managarr" }
- { name: "linux-arm-gnu", os: "ubuntu-latest", target: "arm-unknown-linux-gnueabi", artifact_suffix: "armv6-gnu", use-cross: true, test-bin: "--bin managarr" }
- { name: "linux-arm-musl", os: "ubuntu-latest", target: "arm-unknown-linux-musleabihf", artifact_suffix: "armv6-musl", use-cross: true, test-bin: "--bin managarr" }
- { name: "linux-armv7-gnu", os: "ubuntu-latest", target: "armv7-unknown-linux-gnueabihf", artifact_suffix: "armv7-gnu", use-cross: true, test-bin: "--bin managarr" }
- { name: "linux-armv7-musl", os: "ubuntu-latest", target: "armv7-unknown-linux-musleabihf", artifact_suffix: "armv7-musl", use-cross: true, test-bin: "--bin managarr" }
rust: [stable]
steps: steps:
- name: Check if actor is repository owner - name: Check if actor is repository owner
if: ${{ github.actor != github.repository_owner }} if: ${{ github.actor != github.repository_owner }}
@@ -116,12 +135,366 @@ jobs:
echo "You are not authorized to run this workflow." echo "You are not authorized to run this workflow."
exit 1 exit 1
- name: Checkout - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 1
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable - name: Ensure repository is up-to-date
- uses: katyo/publish-crates@v2 run: |
git fetch --all
git pull
- uses: actions/cache@v3
name: Cache Cargo registry
with: with:
registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }} path: ~/.cargo/registry
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('Cargo.lock') }}
- uses: actions/cache@v3
if: startsWith(matrix.job.name, 'linux-')
with:
path: ~/.cargo/bin
key: ${{ runner.os }}-cargo-bin-${{ hashFiles('.github/workflows/release.yml') }}
- uses: dtolnay/rust-toolchain@stable
name: Set Rust toolchain
with:
targets: ${{ matrix.job.target }}
- uses: taiki-e/setup-cross-toolchain-action@v1
with:
# NB: sets CARGO_BUILD_TARGET evar - do not need --target flag in build
target: ${{ matrix.job.target }}
- uses: taiki-e/install-action@cross
if: ${{ matrix.job.use-cross }}
- name: Installing needed Ubuntu dependencies
if: matrix.job.os == 'ubuntu-latest'
shell: bash
run: |
sudo apt-get -y update
case ${{ matrix.job.target }} in
arm*-linux-*) sudo apt-get -y install gcc-arm-linux-gnueabihf ;;
aarch64-*-linux-*) sudo apt-get -y install gcc-aarch64-linux-gnu ;;
esac
- name: Build
run: cargo build --release --verbose --target=${{ matrix.job.target }} --locked
- name: Verify file
shell: bash
run: |
file target/${{ matrix.job.target }}/release/managarr
- name: Test
if: matrix.job.target != 'aarch64-apple-darwin' && matrix.job.target != 'aarch64-pc-windows-msvc'
run: cargo test --release --verbose --target=${{ matrix.job.target }} ${{ matrix.job.test-bin }}
- name: Packaging final binary (Windows)
if: matrix.job.os == 'windows-latest'
shell: bash
run: |
cd target/${{ matrix.job.target }}/release
BINARY_NAME=managarr.exe
if [ "${{ matrix.job.target }}" != "aarch64-pc-windows-msvc" ]; then
# strip the binary
strip $BINARY_NAME
fi
RELEASE_NAME=managarr-${{ matrix.job.artifact_suffix }}
mkdir -p artifacts
tar czvf $RELEASE_NAME.tar.gz $BINARY_NAME
# create sha checksum files
certutil -hashfile $RELEASE_NAME.tar.gz sha256 | grep -E [A-Fa-f0-9]{64} > $RELEASE_NAME.sha256
echo "RELEASE_NAME=$RELEASE_NAME" >> $GITHUB_ENV
- name: Packaging final binary (macOS and Linux)
if: matrix.job.os != 'windows-latest'
shell: bash
run: |
# set the right strip executable
STRIP="strip";
case ${{ matrix.job.target }} in
arm*-linux-*) STRIP="arm-linux-gnueabihf-strip" ;;
aarch64-*-linux-*) STRIP="aarch64-linux-gnu-strip" ;;
esac;
cd target/${{ matrix.job.target }}/release
BINARY_NAME=managarr
# strip the binary
"$STRIP" "$BINARY_NAME"
RELEASE_NAME=managarr-${{ matrix.job.artifact_suffix }}
tar czvf $RELEASE_NAME.tar.gz $BINARY_NAME
# create sha checksum files
shasum -a 256 $RELEASE_NAME.tar.gz > $RELEASE_NAME.sha256
echo "RELEASE_NAME=$RELEASE_NAME" >> $GITHUB_ENV
- name: Add artifacts
run: |
mkdir -p artifacts
cp target/${{ matrix.job.target }}/release/${{ env.RELEASE_NAME }}.tar.gz artifacts/
cp target/${{ matrix.job.target }}/release/${{ env.RELEASE_NAME }}.sha256 artifacts/
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: artifacts
path: artifacts
publish-github-release:
name: publish-github-release
needs: [build-release-artifacts]
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Download all artifacts
uses: actions/download-artifact@v3
with:
name: artifacts
path: artifacts
- name: Ensure repository is up-to-date
run: |
git fetch --all
git pull
- name: Set environment variables
run: |
release_version="$(cat ./artifacts/release-version)"
echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV
changelog_body="$(cat ./artifacts/changelog.md)"
echo "changelog_body=$(cat artifacts/changelog.md)" >> $GITHUB_ENV
- name: Create a GitHub Release
uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
files: |
artifacts/managarr-macos-arm64.tar.gz
artifacts/managarr-macos-arm64.sha256
artifacts/managarr-macos.tar.gz
artifacts/managarr-macos.sha256
artifacts/managarr-windows.tar.gz
artifacts/managarr-windows.sha256
artifacts/managarr-windows-aarch64.tar.gz
artifacts/managarr-windows-aarch64.sha256
artifacts/managarr-linux.tar.gz
artifacts/managarr-linux.sha256
artifacts/managarr-linux-musl.tar.gz
artifacts/managarr-linux-musl.sha256
artifacts/managarr-aarch64-gnu.tar.gz
artifacts/managarr-aarch64-gnu.sha256
artifacts/managarr-aarch64-musl.tar.gz
artifacts/managarr-aarch64-musl.sha256
artifacts/managarr-armv6-gnu.tar.gz
artifacts/managarr-armv6-gnu.sha256
artifacts/managarr-armv6-musl.tar.gz
artifacts/managarr-armv6-musl.sha256
artifacts/managarr-armv7-gnu.tar.gz
artifacts/managarr-armv7-gnu.sha256
artifacts/managarr-armv7-musl.tar.gz
artifacts/managarr-armv7-musl.sha256
tag_name: v${{ env.RELEASE_VERSION }}
name: "v${{ env.RELEASE_VERSION }}"
body: ${{ env.changelog_body }}
draft: false
prerelease: false
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: artifacts
path: artifacts
# publish-chocolatey-package:
# needs: [publish-github-release]
# name: Publish Chocolatey Package
# runs-on: windows-latest
# steps:
# - name: Checkout repository
# uses: actions/checkout@v4
# with:
# fetch-depth: 1
# - name: Get release artifacts
# uses: actions/download-artifact@v3
# with:
# name: artifacts
# path: artifacts
# - name: Set release assets and version
# shell: pwsh
# run: |
# # Read the first column from the SHA256 file
# $windows_sha = Get-Content ./artifacts/managarr-windows.sha256 | ForEach-Object { $_.Split(' ')[0] }
# Add-Content -Path $env:GITHUB_ENV -Value "WINDOWS_SHA=$windows_sha"
# # Read the release version from the release-version file
# $release_version = Get-Content ./artifacts/release-version
# Add-Content -Path $env:GITHUB_ENV -Value "RELEASE_VERSION=$release_version"
# - name: Validate release environment variables
# run: |
# echo "Release SHA windows: ${{ env.WINDOWS_SHA }}"
# echo "Release version: ${{ env.RELEASE_VERSION }}"
# - name: Package and Publish package to Chocolatey
# run: |
# mkdir ./deployment/chocolatey/tools
# # Run packaging script
# python "./deployment/chocolatey/packager.py" ${{ env.RELEASE_VERSION }} "./deployment/chocolatey/managarr.nuspec.template" "./deployment/chocolatey/managarr.nuspec" ${{ env.WINDOWS_SHA }}
# python "./deployment/chocolatey/packager.py" ${{ env.RELEASE_VERSION }} "./deployment/chocolatey/chocolateyinstall.ps1.template" "./deployment/chocolatey/tools/chocolateyinstall.ps1" ${{ env.WINDOWS_SHA }}
# # Publish to Chocolatey
# cd ./deployment/chocolatey
# choco pack
# echo y | choco install managarr -dv -s .
# $version = managarr --version
# $version = $version -replace " ", "."
# choco push $version.nupkg -s https://push.chocolatey.org/ --api-key ${{ secrets.CHOCOLATEY_API_KEY }};
# publish-homebrew-formula:
# needs: [publish-github-release]
# name: Update Homebrew formulas
# runs-on: ubuntu-latest
# steps:
# - name: Checkout repository
# uses: actions/checkout@v4
# with:
# fetch-depth: 1
# - name: Get release artifacts
# uses: actions/download-artifact@v3
# with:
# name: artifacts
# path: artifacts
# - name: Set release assets and version
# shell: bash
# run: |
# # Set environment variables
# macos_sha="$(cat ./artifacts/managarr-macos.sha256 | awk '{print $1}')"
# echo "MACOS_SHA=$macos_sha" >> $GITHUB_ENV
# macos_sha_arm="$(cat ./artifacts/managarr-macos-arm64.sha256 | awk '{print $1}')"
# echo "MACOS_SHA_ARM=$macos_sha_arm" >> $GITHUB_ENV
# linux_sha="$(cat ./artifacts/managarr-linux-musl.sha256 | awk '{print $1}')"
# echo "LINUX_SHA=$linux_sha" >> $GITHUB_ENV
# release_version="$(cat ./artifacts/release-version)"
# echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV
# - name: Validate release environment variables
# run: |
# echo "Release SHA macos: ${{ env.MACOS_SHA }}"
# echo "Release SHA macos-arm: ${{ env.MACOS_SHA_ARM }}"
# echo "Release SHA linux musl: ${{ env.LINUX_SHA }}"
# echo "Release version: ${{ env.RELEASE_VERSION }}"
# - name: Execute Homebrew packaging script
# run: |
# # run packaging script
# python "./deployment/homebrew/packager.py" ${{ env.RELEASE_VERSION }} "./deployment/homebrew/managarr.rb.template" "./managarr.rb" ${{ env.MACOS_SHA }} ${{ env.MACOS_SHA_ARM }} ${{ env.LINUX_SHA }}
# - name: Push changes to Homebrew tap
# env:
# TOKEN: ${{ secrets.MANAGARR_GITHUB_TOKEN }}
# run: |
# # push to Git
# git config --global user.name "Dark-Alex-17"
# git config --global user.email "alex.j.tusa@gmail.com"
# git clone https://Dark-Alex-17:${{ secrets.MANAGARR_GITHUB_TOKEN }}@github.com/Dark-Alex-17/homebrew-managarr.git
# rm homebrew-managarr/Formula/managarr.rb
# cp managarr.rb homebrew-managarr/Formula
# cd homebrew-managarr
# git add .
# git diff-index --quiet HEAD || git commit -am "Update formula for Managarr release ${{ env.RELEASE_VERSION }}"
# git push https://$TOKEN@github.com/Dark-Alex-17/homebrew-managarr.git
# publish-docker-image:
# needs: [publish-github-release]
# name: Publishing Docker image to Docker Hub
# runs-on: ubuntu-latest
# steps:
# - name: Checkout repository
# uses: actions/checkout@v4
# with:
# fetch-depth: 1
# - name: Get release artifacts
# uses: actions/download-artifact@v3
# with:
# name: artifacts
# path: artifacts
# - name: Set version variable
# run: |
# version="$(cat artifacts/release-version)"
# echo "version=$version" >> $GITHUB_ENV
# - name: Validate release environment variables
# run: |
# echo "Release version: ${{ env.version }}"
# - name: Set up QEMU
# uses: docker/setup-qemu-action@v3
# - name: Set up Docker Buildx
# uses: docker/setup-buildx-action@v3
# - name: Login to Docker Hub
# uses: docker/login-action@v3
# with:
# username: ${{ secrets.DOCKER_USERNAME }}
# password: ${{ secrets.DOCKER_PASSWORD }}
# - name: Push to Docker Hub
# uses: docker/build-push-action@v5
# with:
# context: .
# file: Dockerfile
# platforms: linux/amd64,linux/arm64
# push: true
# tags: darkalex17/managarr:latest, darkalex17/managarr:${{ env.version }}
# publish-crate:
# needs: publish-github-release
# name: Publish Crate
# runs-on: ubuntu-latest
# steps:
# - name: Check if actor is repository owner
# if: ${{ github.actor != github.repository_owner }}
# run: |
# echo "You are not authorized to run this workflow."
# exit 1
# - name: Checkout
# uses: actions/checkout@v4
# with:
# fetch-depth: 0
# - name: Ensure repository is up-to-date
# run: |
# git fetch --all
# git pull
# - uses: actions/cache@v3
# name: Cache Cargo registry
# with:
# path: ~/.cargo/registry
# key: ${{ runner.os }}-cargo-registry-${{ hashFiles('Cargo.lock') }}
# - uses: actions/cache@v3
# with:
# path: ~/.cargo/bin
# key: ${{ runner.os }}-cargo-bin-${{ hashFiles('.github/workflows/release.yml') }}
# - name: Install Rust stable
# uses: dtolnay/rust-toolchain@stable
# - uses: katyo/publish-crates@v2
# with:
# registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
+23
View File
@@ -34,16 +34,20 @@ jobs:
toolchain: [stable, beta] toolchain: [stable, beta]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Install ${{ matrix.toolchain }} - name: Install ${{ matrix.toolchain }}
uses: dtolnay/rust-toolchain@master uses: dtolnay/rust-toolchain@master
with: with:
toolchain: ${{ matrix.toolchain }} toolchain: ${{ matrix.toolchain }}
# enable this ci template to run regardless of whether the lockfile is checked in or not # enable this ci template to run regardless of whether the lockfile is checked in or not
- name: cargo generate-lockfile - name: cargo generate-lockfile
if: hashFiles('Cargo.lock') == '' if: hashFiles('Cargo.lock') == ''
run: cargo generate-lockfile run: cargo generate-lockfile
- name: cargo test --locked - name: cargo test --locked
run: cargo test --locked --all-features --all-targets run: cargo test --locked --all-features --all-targets
minimal-versions: minimal-versions:
# This action chooses the oldest version of the dependencies permitted by Cargo.toml to ensure # This action chooses the oldest version of the dependencies permitted by Cargo.toml to ensure
# that this crate is compatible with the minimal version that this crate and its dependencies # that this crate is compatible with the minimal version that this crate and its dependencies
@@ -71,18 +75,25 @@ jobs:
name: ubuntu / stable / minimal-versions name: ubuntu / stable / minimal-versions
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Install Rust stable - name: Install Rust stable
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@stable
- name: Install nightly for -Zdirect-minimal-versions - name: Install nightly for -Zdirect-minimal-versions
uses: dtolnay/rust-toolchain@nightly uses: dtolnay/rust-toolchain@nightly
- name: rustup default stable - name: rustup default stable
run: rustup default stable run: rustup default stable
- name: cargo update -Zdirect-minimal-versions - name: cargo update -Zdirect-minimal-versions
run: cargo +nightly update -Zdirect-minimal-versions run: cargo +nightly update -Zdirect-minimal-versions
- name: cargo test - name: cargo test
run: cargo test --locked --all-features --all-targets run: cargo test --locked --all-features --all-targets
- name: Cache Cargo dependencies - name: Cache Cargo dependencies
uses: Swatinem/rust-cache@v2 uses: Swatinem/rust-cache@v2
os-check: os-check:
# run cargo test on mac and windows # run cargo test on mac and windows
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
@@ -100,15 +111,20 @@ jobs:
# if: runner.os == 'Windows' # if: runner.os == 'Windows'
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Install Rust stable - name: Install Rust stable
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@stable
- name: cargo generate-lockfile - name: cargo generate-lockfile
if: hashFiles('Cargo.lock') == '' if: hashFiles('Cargo.lock') == ''
run: cargo generate-lockfile run: cargo generate-lockfile
- name: cargo test - name: cargo test
run: cargo test --locked --all-features --all-targets run: cargo test --locked --all-features --all-targets
- name: Cache Cargo dependencies - name: Cache Cargo dependencies
uses: Swatinem/rust-cache@v2 uses: Swatinem/rust-cache@v2
coverage: coverage:
# use llvm-cov to build and collect coverage and outputs in a format that # use llvm-cov to build and collect coverage and outputs in a format that
# is compatible with codecov.io # is compatible with codecov.io
@@ -136,21 +152,28 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Install Rust stable - name: Install Rust stable
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@stable
with: with:
components: llvm-tools-preview components: llvm-tools-preview
- name: cargo install cargo-llvm-cov - name: cargo install cargo-llvm-cov
uses: taiki-e/install-action@cargo-llvm-cov uses: taiki-e/install-action@cargo-llvm-cov
- name: cargo generate-lockfile - name: cargo generate-lockfile
if: hashFiles('Cargo.lock') == '' if: hashFiles('Cargo.lock') == ''
run: cargo generate-lockfile run: cargo generate-lockfile
- name: cargo llvm-cov - name: cargo llvm-cov
run: cargo llvm-cov --locked --all-features --lcov --output-path lcov.info run: cargo llvm-cov --locked --all-features --lcov --output-path lcov.info
- name: Record Rust version - name: Record Rust version
run: echo "RUST=$(rustc --version)" >> "$GITHUB_ENV" run: echo "RUST=$(rustc --version)" >> "$GITHUB_ENV"
- name: Cache Cargo dependencies - name: Cache Cargo dependencies
uses: Swatinem/rust-cache@v2 uses: Swatinem/rust-cache@v2
- name: Upload to codecov.io - name: Upload to codecov.io
uses: codecov/codecov-action@v4 uses: codecov/codecov-action@v4
with: with:
+171
View File
@@ -5,6 +5,177 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## v0.4.2 (2024-12-21)
### Fix
- Revert failed release [skip ci]
- **sonarr**: Pass the series ID alongside all UpdateAndScan events when publishing to the networking channel
- **sonarr**: pass the series ID alongside all TriggerAutomaticSeriesSearch events when publishing to the networking channel
- **sonarr**: Pass the series ID and season number alongside all TriggerAutomaticSeasonSearch events when publishing to the networking channel
- **sonarr**: Pass the episode ID alongside all TriggerAutomaticEpisodeSearch events when publishing to the networking channel
- **sonarr**: Pass the episode ID alongside all ToggleEpisodeMonitoring events when publishing to the networking channel
- **sonarr**: Pass the series ID and season number alongside all toggle season monitoring events when publishing to the networking channel
- **sonarr**: Pass the indexer ID directly alongside all TestIndexer events when publishing to the networking channel
- **sonarr**: Provide the task name directly alongside all StartTask events when publishing to the networking channel
- **sonarr**: Pass the search query directly to the networking channel when searching for a new series
- **sonarr**: Pass the series ID alongside all GetSeriesHistory events when publishing to the networking channel
- **sonarr**: Pass the series ID alongside all GetSeriesDetails events when publishing to the networking channel
- **sonarr**: Pass series ID and season number alongside all ManualSeasonSearch events when publishing to the networking channel
- **sonarr**: Provide the series ID and season number alongside all GetSeasonHistory events when publishing to the networking channel
- **sonarr**: Pass the episode ID alongside all ManualEpisodeSearch events when publishing to the networking channel
- **sonarr**: Pass events alongside all GetLogs events when publishing to the networking channel
- **sonarr**: Pass the episode ID alongside all GetEpisodeHistory events when publishing to the networking channel
- **sonarr**: Pass series ID alongside all GetEpisodeFiles events when publishing to the networking channel
- **sonarr**: Pass series ID alognside all GetEpisodes events when publishing to the networking channel
- **sonarr**: Pass the episode ID alongside all GetEpisodeDetails events when publishing to the networking channel
- **sonarr**: Pass history events alongside all GetHistory events when publishing to the networking channel
- **sonarr**: Construct and pass edit series parameters alongside all EditSeries events when publishing to the networking channel
- **sonarr**: Construct and pass edit indexer parameters alongside all EditIndexer events when publishing to the networking channel
- **sonarr**: Construct and pass edit all indexer settings alongside all EditAllIndexerSettings events when publishing to the networking channel
- **sonarr**: Construct and pass delete series params alongside all DeleteSeries events when publishing to the networking channel
- **sonarr**: Corrected a bug that would cause a crash if a user spams the ESC key while searching for a new series and the search results are still loading
- **sonarr**: Pass the root folder ID alongside all DeleteRootFolder events when publishing to the networking channel
- **sonarr**: Pass the indexer ID alongside all DeleteIndexer events when publishing to the networking channel
- **sonarr**: Pass the episode file ID alongside all DeleteEpisodeFile events when publishing to the networking channel
- **sonarr**: Pass the download ID alongside all DeleteDownload events published to the networking channel
- **sonarr**: Pass the blocklist item ID alongside the DeleteBlocklistItem event when publishing to the networking channel
- **sonarr**: Construct and pass the add series body alongside AddSeries events when publishing to the networking channel
- **sonarr**: Construct and pass the AddRootFolderBody alongside all AddRootFolder events when publishing to the networking channel
- **radarr**: Pass the movie ID alongside all UpdateAndScan events published to the networking channel
- **radarr**: Provide the movie ID alongside all TriggerAutomaticMovieSearch events when publishing to the networking channel
- **radarr**: Pass in the indexer id with all TestIndexer events when publishing to the networking channel
- **radarr**: Pass in the task name alongside the StartTask event when publishing to the networking channel
- **radarr**: Pass in the search query for the SearchNewMovie event when publishing to the networking channel
- **radarr**: Pass in the movie ID alongside the GetReleases event when publishing to the networking channel
- **radarr**: Pass in the movie ID alongside the GetMovieHistory event when publishing to the networking channel
- **radarr**: Pass the movie ID in alongside the GetMovieDetaisl event when publishing to the networking channel
- **radarr**: Provide the movie id alongside the GetMovieCredits event when publishing to the networking channel
- **radarr**: Pass the number of log events to fetch in with the GetLogs event when publishing to the networking channel
- **radarr**: Construct and pass the edit movie parameters alongside the EditMovie event when publishing to the networking channel
- **radarr**: Construct and pass params when publishing the EditIndexer event to the networking channel
- **radarr**: Construct and pass edit collection parameters alongside the EditCollection event when publishing to the networking channel
- **radarr**: Build and pass the edit indexer settings body with the EditAllIndexerSettings event when publishing to the networking channel
- **radarr**: Send the parameters alongside the DownloadRelease event when publishing to the networking channel
- **radarr**: Pass the root folder ID in with the DeleteRootFolder event when publishing to the networking channel
- Pass the delete movie params in with the DeleteMovie event when publishing to the networking channel
- Pass the indexer ID in with the DeleteIndexer event when sending to the networking channel
- Pass the download ID directly in the DeleteDownload event when publishing into the networking channel
- Blocklist Item ID passed in the DeleteBlocklistItem event when sent to the networking channel
- AddRootFolderBody now constructed prior to AddRootFolder event being sent down the network channel
- Cancel all requests when switching Servarr tabs to both improve performance and fix issue #15
- **add_movie_handler_tests**: Added in a forgotten test for the build_add_movie_body function
- Missing tagged version of docker builds in release flow
- AddMovie Radarr event is now populated in the dispatch thread before being sent to the network thread
- dynamically load servarrs in UI based on what configs are provided
## v0.4.1 (2024-12-14)
### Feat
- **docs**: Updated the README with new screeshots for the Sonarr release
- **handler**: Support for toggling the monitoring status of a specified episode in the Sonarr UI
- **handlers**: Support for toggling the monitoring status of a season in the Sonarr UI
- **keybindings**: Added a new keybinding for toggling the monitoring of a highlighted table item
- **cli**: Support for toggling monitoring on a specific episode in Sonarr
- **network**: Support for toggling the monitoring status of an episode in Sonarr
- **cli**: Support for toggling monitoring for a specific season in Sonarr
- **network**: Support for toggling monitoring/unmonitoring a season
- **handlers**: Support for the episode details popup
- **ui**: Support for the episode details UI
- **handler**: Full handler support for the Season details UI in Sonarr
- **ui**: Sonarr support for viewing season details
- **cli**: Sonarr support for fetching a list of all episode files for a given series ID
- **app**: Dispatch support for Season Details to fetch both the current downloads as well as the episode files to match qualities to them
- **network**: Support for fetching all episode files for a given series
- **app**: Model and modal support for the season and episode details popups
- **cli**: Sonarr support for fetching season history events
- **network**: Sonarr support for fetching season history
- **ui**: Sonarr support for the series details popup
- **ui**: Sonarr support for editing a series from within the series details popup
- **ui**: Sonarr Series details UI is now available
- **ui**: Full Sonarr system tab support
- **handler**: System handler support for Sonarr
- **ui**: Full Sonarr support for the indexer tab
- **ui**: Support for modifying the indexer priority in Radarr
- **handler**: Full indexer tab handler support
- **ui**: Root folder tab support
- **handlers**: Support for root folder actions
- **ui**: History tab support
- **handler**: History tab support
- **ui**: Blocklist UI support
- **handler**: Wired in the blocklist handler to the main handlers
- **handler**: Blocklist handler support
- **ui**: Downloads tab support
- **handler**: Download tab support
- **ui**: Edit series support
- **handler**: Edit series support
- **ui**: Add series support Sonarr
- **handler**: Add series support for Sonarr
- **ui**: Delete a series
- **handler**: Support for deleting a series in Sonarr
- **ui**: Support for the Series table
- **handlers**: Sonarr key support for the Series table
- **models**: Added the necessary contextual help and tabs for the Sonarr UI
- **ui**: Initial UI support for switching to Sonarr tabs
- **app**: Dispatch support for all relevant Sonarr blocks
### Fix
- **blocklist_handler**: Fixed a breaking change between Sonarr v3 and v4
- **style**: Addressed linter complaints on formatting
- Implemented a handful of fixes that are breaking changes between Sonarr v3 and v4
- **handler_tests**: Fixed all delegation tests to have initial conditions set properly
- **ui**: Fixed a bug that requires a minimum height for all popups so all error messages and other simple popups appear
- **handler**: Fixed a bug in the history handler that wouldn't reset the filter or search if a user hit 'esc' on the History tab
- **ui**: Fix the System Details Tasks popup to be navigable in both Sonarr and Radarr
- **ui**: Fixed a potential rare bug in the UI where the application would panic if the height of the downloads window is 0.
### Refactor
- **network**: Changed the toggle episode monitoring handler to simply return empty since the response is always empty from Sonarr
- **ui**: Tweaked some of the color schemes in the series table
- Fixed a couple of typos in some test function names
- **handlers**: Refactored the handlers to all use the handle_table_events macro when appropriate and created tests for the macro so tests don't have to be duplicated across each handler
- **ui**: Simplified the popup delegation so all future UI is easier to implement
- **indexers_handler**: Use the new handle_table_events macro
- **root_folders_handler**: Use the new handle_table_events macro
- **blocklist_handler**: Use the new handle_table_events macro
- **downloads_handler**: Use the new handle_table_events macro
- **collection_details_handler**: use the new handle_table_events macro
- **collections_handler**: Use the new handle_table_events macro
- **movie_details_handler**: Use the new handle_table_events macro
- **library_handler**: Radarr use the new handle_table_events macro
- **indexers_handler**: Use the new handle_table_events macro
- **indexers_handler**: Use the new handle_table_events macro
- **root_folder_handler**: Use the new handle_table_events macro
- **history_handler**: Use the new handle_table_event macro
- **blocklist_handler**: Use the new handle_table_events macro
- **downloads_handler**: Use the new handle_table_events macro
- **series_details_handler**: Use the new handle_table_events macro
- **handler**: Created a macro to handle all table key events to reduce code duplication and make future implementations faster; Only refactored the Sonarr library to use it thus far
- **ui**: all table search and filter functionality is now available directly through the ManagarrTable widget to make life easier moving forward
- **keys**: Created a auto search key instead of reusing the existing search key to make things easier
- **BlockSelectionState**: Refactored so selection of blocks in 2x2 grids is more intuitive and added left() and right() methods to aid this effort.
### Perf
- Improved performance by optimizing API calls to only refresh when the tick prompts a refresh. All UI is now significantly faster
## v0.3.7 (2024-11-26)
### Fix
- **ci**: Forgot to also pull in the most recent changes [skip ci]
## v0.3.6 (2024-11-26)
### Fix
- **ci**: Ensure the Release Crate job fetches the most recent commit before publishing the crate [skip ci]
## v0.3.4 (2024-11-26)
## v0.3.3 (2024-11-26) ## v0.3.3 (2024-11-26)
### Fix ### Fix
Generated
+171 -146
View File
@@ -28,9 +28,9 @@ dependencies = [
[[package]] [[package]]
name = "allocator-api2" name = "allocator-api2"
version = "0.2.20" version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]] [[package]]
name = "android-tzdata" name = "android-tzdata"
@@ -98,9 +98,9 @@ dependencies = [
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.93" version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7"
[[package]] [[package]]
name = "arc-swap" name = "arc-swap"
@@ -142,7 +142,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.89", "syn 2.0.90",
] ]
[[package]] [[package]]
@@ -195,9 +195,9 @@ checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]] [[package]]
name = "bstr" name = "bstr"
version = "1.11.0" version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a68f1f47cdf0ec8ee4b941b2eee2a80cb796db73118c0dd09ac63fbe405be22" checksum = "786a307d683a5bf92e6fd5fd69a7eb613751668d1d8d67d802846dfe367c62c8"
dependencies = [ dependencies = [
"memchr", "memchr",
"regex-automata", "regex-automata",
@@ -218,9 +218,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.8.0" version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b"
[[package]] [[package]]
name = "cargo-husky" name = "cargo-husky"
@@ -245,9 +245,9 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.1" version = "1.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" checksum = "c31a0499c1dc64f458ad13872de75c0eb7e3fdb0e67964610c914b034fc5956e"
dependencies = [ dependencies = [
"shlex", "shlex",
] ]
@@ -266,9 +266,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]] [[package]]
name = "chrono" name = "chrono"
version = "0.4.38" version = "0.4.39"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
dependencies = [ dependencies = [
"android-tzdata", "android-tzdata",
"iana-time-zone", "iana-time-zone",
@@ -281,9 +281,9 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.21" version = "4.5.23"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@@ -291,9 +291,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.5.21" version = "4.5.23"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
@@ -303,9 +303,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_complete" name = "clap_complete"
version = "4.5.38" version = "4.5.40"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9647a559c112175f17cf724dc72d3645680a883c58481332779192b0d8e7a01" checksum = "ac2e663e3e3bed2d32d065a8404024dad306e699a04263ec59919529f803aee9"
dependencies = [ dependencies = [
"clap", "clap",
] ]
@@ -319,14 +319,14 @@ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.89", "syn 2.0.90",
] ]
[[package]] [[package]]
name = "clap_lex" name = "clap_lex"
version = "0.7.3" version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
[[package]] [[package]]
name = "colorchoice" name = "colorchoice"
@@ -336,12 +336,12 @@ checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
[[package]] [[package]]
name = "colored" name = "colored"
version = "2.1.0" version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
dependencies = [ dependencies = [
"lazy_static", "lazy_static",
"windows-sys 0.48.0", "windows-sys 0.59.0",
] ]
[[package]] [[package]]
@@ -372,15 +372,15 @@ dependencies = [
[[package]] [[package]]
name = "console" name = "console"
version = "0.15.8" version = "0.15.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b"
dependencies = [ dependencies = [
"encode_unicode", "encode_unicode",
"lazy_static",
"libc", "libc",
"unicode-width 0.1.14", "once_cell",
"windows-sys 0.52.0", "unicode-width 0.2.0",
"windows-sys 0.59.0",
] ]
[[package]] [[package]]
@@ -455,7 +455,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"strsim", "strsim",
"syn 2.0.89", "syn 2.0.90",
] ]
[[package]] [[package]]
@@ -466,7 +466,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
dependencies = [ dependencies = [
"darling_core", "darling_core",
"quote", "quote",
"syn 2.0.89", "syn 2.0.90",
] ]
[[package]] [[package]]
@@ -489,12 +489,30 @@ dependencies = [
"syn 1.0.109", "syn 1.0.109",
] ]
[[package]]
name = "derive_setters"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e8ef033054e131169b8f0f9a7af8f5533a9436fadf3c500ed547f730f07090d"
dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 2.0.90",
]
[[package]] [[package]]
name = "destructure_traitobject" name = "destructure_traitobject"
version = "0.2.0" version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c877555693c14d2f84191cfd3ad8582790fc52b5e2274b40b59cf5f5cea25c7" checksum = "3c877555693c14d2f84191cfd3ad8582790fc52b5e2274b40b59cf5f5cea25c7"
[[package]]
name = "deunicode"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "339544cc9e2c4dc3fc7149fd630c5f22263a4fdf18a98afd0075784968b5cf00"
[[package]] [[package]]
name = "diff" name = "diff"
version = "0.1.13" version = "0.1.13"
@@ -557,7 +575,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.89", "syn 2.0.90",
] ]
[[package]] [[package]]
@@ -580,9 +598,9 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]] [[package]]
name = "encode_unicode" name = "encode_unicode"
version = "0.3.6" version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
[[package]] [[package]]
name = "encoding_rs" name = "encoding_rs"
@@ -601,19 +619,19 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]] [[package]]
name = "errno" name = "errno"
version = "0.3.9" version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys 0.52.0", "windows-sys 0.59.0",
] ]
[[package]] [[package]]
name = "fastrand" name = "fastrand"
version = "2.2.0" version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]] [[package]]
name = "fnv" name = "fnv"
@@ -623,9 +641,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]] [[package]]
name = "foldhash" name = "foldhash"
version = "0.1.3" version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f"
[[package]] [[package]]
name = "foreign-types" name = "foreign-types"
@@ -713,7 +731,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.89", "syn 2.0.90",
] ]
[[package]] [[package]]
@@ -811,17 +829,11 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hermit-abi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
[[package]] [[package]]
name = "http" name = "http"
version = "1.1.0" version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea"
dependencies = [ dependencies = [
"bytes", "bytes",
"fnv", "fnv",
@@ -887,9 +899,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]] [[package]]
name = "hyper" name = "hyper"
version = "1.5.1" version = "1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97818827ef4f364230e16705d4706e2897df2bb60617d6ca15d598025a3c481f" checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-channel", "futures-channel",
@@ -908,9 +920,9 @@ dependencies = [
[[package]] [[package]]
name = "hyper-rustls" name = "hyper-rustls"
version = "0.27.3" version = "0.27.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2"
dependencies = [ dependencies = [
"futures-util", "futures-util",
"http", "http",
@@ -1096,7 +1108,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.89", "syn 2.0.90",
] ]
[[package]] [[package]]
@@ -1128,9 +1140,9 @@ dependencies = [
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.6.0" version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown", "hashbrown",
@@ -1157,16 +1169,16 @@ checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5"
[[package]] [[package]]
name = "instability" name = "instability"
version = "0.3.3" version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b829f37dead9dc39df40c2d3376c179fdfd2ac771f53f55d3c30dc096a3c0c6e" checksum = "898e106451f7335950c9cc64f8ec67b5f65698679ac67ed00619aeef14e1cf75"
dependencies = [ dependencies = [
"darling", "darling",
"indoc", "indoc",
"pretty_assertions", "pretty_assertions",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.89", "syn 2.0.90",
] ]
[[package]] [[package]]
@@ -1198,10 +1210,11 @@ checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.72" version = "0.3.76"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7"
dependencies = [ dependencies = [
"once_cell",
"wasm-bindgen", "wasm-bindgen",
] ]
@@ -1213,9 +1226,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.165" version = "0.2.169"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcb4d3d38eab6c5239a362fa8bae48c03baf980a6e7079f063942d563ef3533e" checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
[[package]] [[package]]
name = "libredox" name = "libredox"
@@ -1303,7 +1316,7 @@ dependencies = [
[[package]] [[package]]
name = "managarr" name = "managarr"
version = "0.3.3" version = "0.4.2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"assert_cmd", "assert_cmd",
@@ -1319,6 +1332,8 @@ dependencies = [
"crossterm", "crossterm",
"ctrlc", "ctrlc",
"derivative", "derivative",
"derive_setters",
"deunicode",
"dirs-next", "dirs-next",
"human-panic", "human-panic",
"indicatif", "indicatif",
@@ -1329,6 +1344,8 @@ dependencies = [
"managarr-tree-widget", "managarr-tree-widget",
"mockall", "mockall",
"mockito", "mockito",
"openssl",
"paste",
"pretty_assertions", "pretty_assertions",
"ratatui", "ratatui",
"regex", "regex",
@@ -1368,20 +1385,19 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.8.0" version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394"
dependencies = [ dependencies = [
"adler2", "adler2",
] ]
[[package]] [[package]]
name = "mio" name = "mio"
version = "1.0.2" version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
dependencies = [ dependencies = [
"hermit-abi",
"libc", "libc",
"log", "log",
"wasi", "wasi",
@@ -1411,7 +1427,7 @@ dependencies = [
"cfg-if", "cfg-if",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.89", "syn 2.0.90",
] ]
[[package]] [[package]]
@@ -1499,9 +1515,9 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
[[package]] [[package]]
name = "object" name = "object"
version = "0.36.5" version = "0.36.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
@@ -1535,7 +1551,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.89", "syn 2.0.90",
] ]
[[package]] [[package]]
@@ -1544,6 +1560,15 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-src"
version = "300.4.1+3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "faa4eac4138c62414b5622d1b31c5c304f34b406b013c079c2bbc652fdd6678c"
dependencies = [
"cc",
]
[[package]] [[package]]
name = "openssl-sys" name = "openssl-sys"
version = "0.9.104" version = "0.9.104"
@@ -1552,6 +1577,7 @@ checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741"
dependencies = [ dependencies = [
"cc", "cc",
"libc", "libc",
"openssl-src",
"pkg-config", "pkg-config",
"vcpkg", "vcpkg",
] ]
@@ -1573,9 +1599,9 @@ dependencies = [
[[package]] [[package]]
name = "os_info" name = "os_info"
version = "3.8.2" version = "3.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae99c7fa6dd38c7cafe1ec085e804f8f555a2f8659b0dbe03f1f9963a9b51092" checksum = "e5ca711d8b83edbb00b44d504503cd247c9c0bd8b0fa2694f2a1a3d8165379ce"
dependencies = [ dependencies = [
"log", "log",
"serde", "serde",
@@ -1658,9 +1684,9 @@ dependencies = [
[[package]] [[package]]
name = "predicates" name = "predicates"
version = "3.1.2" version = "3.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e9086cc7640c29a356d1a29fd134380bee9d8f79a17410aa76e7ad295f42c97" checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573"
dependencies = [ dependencies = [
"anstyle", "anstyle",
"difflib", "difflib",
@@ -1669,15 +1695,15 @@ dependencies = [
[[package]] [[package]]
name = "predicates-core" name = "predicates-core"
version = "1.0.8" version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae8177bee8e75d6846599c6b9ff679ed51e882816914eec639944d7c9aa11931" checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa"
[[package]] [[package]]
name = "predicates-tree" name = "predicates-tree"
version = "1.0.11" version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41b740d195ed3166cd147c8047ec98db0e22ec019eb8eeb76d343b795304fb13" checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c"
dependencies = [ dependencies = [
"predicates-core", "predicates-core",
"termtree", "termtree",
@@ -1774,9 +1800,9 @@ dependencies = [
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.5.7" version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834"
dependencies = [ dependencies = [
"bitflags", "bitflags",
] ]
@@ -1911,7 +1937,7 @@ dependencies = [
"regex", "regex",
"relative-path", "relative-path",
"rustc_version", "rustc_version",
"syn 2.0.89", "syn 2.0.90",
"unicode-ident", "unicode-ident",
] ]
@@ -1932,22 +1958,22 @@ dependencies = [
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.38.41" version = "0.38.42"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"errno", "errno",
"libc", "libc",
"linux-raw-sys", "linux-raw-sys",
"windows-sys 0.52.0", "windows-sys 0.59.0",
] ]
[[package]] [[package]]
name = "rustls" name = "rustls"
version = "0.23.18" version = "0.23.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c9cc1d47e243d655ace55ed38201c19ae02c148ae56412ab8750e8f0166ab7f" checksum = "5065c3f250cbd332cd894be57c40fa52387247659b14a2d6041d121547903b1b"
dependencies = [ dependencies = [
"once_cell", "once_cell",
"rustls-pki-types", "rustls-pki-types",
@@ -1967,9 +1993,9 @@ dependencies = [
[[package]] [[package]]
name = "rustls-pki-types" name = "rustls-pki-types"
version = "1.10.0" version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37"
[[package]] [[package]]
name = "rustls-webpki" name = "rustls-webpki"
@@ -2024,9 +2050,9 @@ dependencies = [
[[package]] [[package]]
name = "security-framework-sys" name = "security-framework-sys"
version = "2.12.1" version = "2.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2" checksum = "1863fd3768cd83c56a7f60faa4dc0d403f1b6df0a38c3c25f44b7894e45370d5"
dependencies = [ dependencies = [
"core-foundation-sys", "core-foundation-sys",
"libc", "libc",
@@ -2034,15 +2060,15 @@ dependencies = [
[[package]] [[package]]
name = "semver" name = "semver"
version = "1.0.23" version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.215" version = "1.0.216"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
@@ -2059,20 +2085,20 @@ dependencies = [
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.215" version = "1.0.216"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.89", "syn 2.0.90",
] ]
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.133" version = "1.0.134"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d"
dependencies = [ dependencies = [
"itoa", "itoa",
"memchr", "memchr",
@@ -2173,9 +2199,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]] [[package]]
name = "socket2" name = "socket2"
version = "0.5.7" version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys 0.52.0", "windows-sys 0.52.0",
@@ -2224,7 +2250,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"rustversion", "rustversion",
"syn 2.0.89", "syn 2.0.90",
] ]
[[package]] [[package]]
@@ -2246,9 +2272,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.89" version = "2.0.90"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -2272,7 +2298,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.89", "syn 2.0.90",
] ]
[[package]] [[package]]
@@ -2311,9 +2337,9 @@ dependencies = [
[[package]] [[package]]
name = "termtree" name = "termtree"
version = "0.4.1" version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683"
[[package]] [[package]]
name = "thiserror" name = "thiserror"
@@ -2332,7 +2358,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.89", "syn 2.0.90",
] ]
[[package]] [[package]]
@@ -2347,9 +2373,9 @@ dependencies = [
[[package]] [[package]]
name = "time" name = "time"
version = "0.3.36" version = "0.3.37"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21"
dependencies = [ dependencies = [
"deranged", "deranged",
"libc", "libc",
@@ -2378,9 +2404,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.41.1" version = "1.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551"
dependencies = [ dependencies = [
"backtrace", "backtrace",
"bytes", "bytes",
@@ -2402,7 +2428,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.89", "syn 2.0.90",
] ]
[[package]] [[package]]
@@ -2417,20 +2443,19 @@ dependencies = [
[[package]] [[package]]
name = "tokio-rustls" name = "tokio-rustls"
version = "0.26.0" version = "0.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37"
dependencies = [ dependencies = [
"rustls", "rustls",
"rustls-pki-types",
"tokio", "tokio",
] ]
[[package]] [[package]]
name = "tokio-util" name = "tokio-util"
version = "0.7.12" version = "0.7.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-core", "futures-core",
@@ -2481,9 +2506,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
[[package]] [[package]]
name = "tracing" name = "tracing"
version = "0.1.40" version = "0.1.41"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
dependencies = [ dependencies = [
"pin-project-lite", "pin-project-lite",
"tracing-core", "tracing-core",
@@ -2645,9 +2670,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.95" version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"once_cell", "once_cell",
@@ -2656,36 +2681,36 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-backend" name = "wasm-bindgen-backend"
version = "0.2.95" version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"log", "log",
"once_cell",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.89", "syn 2.0.90",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
[[package]] [[package]]
name = "wasm-bindgen-futures" name = "wasm-bindgen-futures"
version = "0.4.45" version = "0.4.49"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"js-sys", "js-sys",
"once_cell",
"wasm-bindgen", "wasm-bindgen",
"web-sys", "web-sys",
] ]
[[package]] [[package]]
name = "wasm-bindgen-macro" name = "wasm-bindgen-macro"
version = "0.2.95" version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe"
dependencies = [ dependencies = [
"quote", "quote",
"wasm-bindgen-macro-support", "wasm-bindgen-macro-support",
@@ -2693,28 +2718,28 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro-support" name = "wasm-bindgen-macro-support"
version = "0.2.95" version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.89", "syn 2.0.90",
"wasm-bindgen-backend", "wasm-bindgen-backend",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
[[package]] [[package]]
name = "wasm-bindgen-shared" name = "wasm-bindgen-shared"
version = "0.2.95" version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6"
[[package]] [[package]]
name = "web-sys" name = "web-sys"
version = "0.3.72" version = "0.3.76"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc"
dependencies = [ dependencies = [
"js-sys", "js-sys",
"wasm-bindgen", "wasm-bindgen",
@@ -2986,7 +3011,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.89", "syn 2.0.90",
"synstructure", "synstructure",
] ]
@@ -3008,7 +3033,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.89", "syn 2.0.90",
] ]
[[package]] [[package]]
@@ -3028,7 +3053,7 @@ checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.89", "syn 2.0.90",
"synstructure", "synstructure",
] ]
@@ -3057,5 +3082,5 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.89", "syn 2.0.90",
] ]
+9 -2
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "managarr" name = "managarr"
version = "0.3.3" version = "0.4.2"
authors = ["Alex Clarke <alex.j.tusa@gmail.com>"] authors = ["Alex Clarke <alex.j.tusa@gmail.com>"]
description = "A TUI and CLI to manage your Servarrs" description = "A TUI and CLI to manage your Servarrs"
keywords = ["managarr", "ratatui", "dashboard", "servarr", "tui"] keywords = ["managarr", "ratatui", "dashboard", "servarr", "tui"]
@@ -36,7 +36,10 @@ strum = { version = "0.26.3", features = ["derive"] }
strum_macros = "0.26.4" strum_macros = "0.26.4"
tokio = { version = "1.36.0", features = ["full"] } tokio = { version = "1.36.0", features = ["full"] }
tokio-util = "0.7.8" tokio-util = "0.7.8"
ratatui = { version = "0.29.0", features = ["all-widgets"] } ratatui = { version = "0.29.0", features = [
"all-widgets",
"unstable-widget-ref",
] }
urlencoding = "2.1.2" urlencoding = "2.1.2"
clap = { version = "4.5.20", features = ["derive", "cargo", "env"] } clap = { version = "4.5.20", features = ["derive", "cargo", "env"] }
clap_complete = "4.5.33" clap_complete = "4.5.33"
@@ -47,6 +50,10 @@ async-trait = "0.1.83"
dirs-next = "2.0.0" dirs-next = "2.0.0"
managarr-tree-widget = "0.24.0" managarr-tree-widget = "0.24.0"
indicatif = "0.17.9" indicatif = "0.17.9"
derive_setters = "0.1.6"
deunicode = "1.6.0"
paste = "1.0.15"
openssl = { version = "0.10.68", features = ["vendored"] }
[dev-dependencies] [dev-dependencies]
assert_cmd = "2.0.16" assert_cmd = "2.0.16"
+5 -5
View File
@@ -1,4 +1,4 @@
FROM clux/muslrust:stable AS builder FROM rust:1.82 AS builder
WORKDIR /usr/src WORKDIR /usr/src
# Download and compile Rust dependencies in an empty project and cache as a separate Docker layer # Download and compile Rust dependencies in an empty project and cache as a separate Docker layer
@@ -6,17 +6,17 @@ RUN USER=root cargo new --bin managarr-temp
WORKDIR /usr/src/managarr-temp WORKDIR /usr/src/managarr-temp
COPY Cargo.* . COPY Cargo.* .
RUN cargo build --release --target x86_64-unknown-linux-musl RUN cargo build --release
# remove src from empty project # remove src from empty project
RUN rm -r src RUN rm -r src
COPY src ./src COPY src ./src
# remove previous deps # remove previous deps
RUN rm ./target/x86_64-unknown-linux-musl/release/deps/managarr* RUN rm ./target/release/deps/managarr*
RUN --mount=type=cache,target=/volume/target \ RUN --mount=type=cache,target=/volume/target \
--mount=type=cache,target=/root/.cargo/registry \ --mount=type=cache,target=/root/.cargo/registry \
cargo build --release --target x86_64-unknown-linux-musl --bin managarr cargo build --release --bin managarr
RUN mv target/x86_64-unknown-linux-musl/release/managarr . RUN mv target/release/managarr .
FROM debian:stable-slim FROM debian:stable-slim
+105 -31
View File
@@ -8,10 +8,12 @@
![Release](https://img.shields.io/github/v/release/Dark-Alex-17/managarr?color=%23c694ff) ![Release](https://img.shields.io/github/v/release/Dark-Alex-17/managarr?color=%23c694ff)
[![codecov](https://codecov.io/gh/Dark-Alex-17/managarr/graph/badge.svg?token=33G179TW67)](https://codecov.io/gh/Dark-Alex-17/managarr) [![codecov](https://codecov.io/gh/Dark-Alex-17/managarr/graph/badge.svg?token=33G179TW67)](https://codecov.io/gh/Dark-Alex-17/managarr)
![Crate.io downloads](https://img.shields.io/crates/d/managarr?label=Crate%20downloads) ![Crate.io downloads](https://img.shields.io/crates/d/managarr?label=Crate%20downloads)
[![GitHub Downloads](https://img.shields.io/github/downloads/Dark-Alex-17/managarr/total.svg?label=GitHub%20downloads)](https://github.com/Dark-Alex-17/managarr/releases)
![Docker pulls](https://img.shields.io/docker/pulls/darkalex17/managarr?label=Docker%20downloads)
Managarr is a TUI and CLI to help you manage your HTPC (Home Theater PC). Built with 🤎 in Rust! Managarr is a TUI and CLI to help you manage your HTPC (Home Theater PC). Built with 🤎 in Rust!
![library](screenshots/library.png) ![library](screenshots/sonarr/sonarr_library.png)
## What Servarrs are supported? ## What Servarrs are supported?
@@ -32,7 +34,10 @@ Simply run the following command to start a demo:
curl https://raw.githubusercontent.com/Dark-Alex-17/managarr-demo/main/managarr-demo.sh > /tmp/managarr-demo.sh && bash /tmp/managarr-demo.sh curl https://raw.githubusercontent.com/Dark-Alex-17/managarr-demo/main/managarr-demo.sh > /tmp/managarr-demo.sh && bash /tmp/managarr-demo.sh
``` ```
Alternatively, you can try out the demo container without downloading anything by visiting the [Managarr Demo site](https://managarr-demo.alexjclarke.com).
## Installation ## Installation
### Cargo ### Cargo
If you have Cargo installed, then you can install Managarr from Crates.io: If you have Cargo installed, then you can install Managarr from Crates.io:
@@ -46,13 +51,74 @@ cargo install --locked managarr
### Docker ### Docker
Run Managarr as a docker container by mounting your `config.yml` file to `/root/.config/managarr/config.yml`. For example: Run Managarr as a docker container by mounting your `config.yml` file to `/root/.config/managarr/config.yml`. For example:
```shell ```shell
docker run --rm -it -v ~/.config/managarr/config.yml:/root/.config/managarr/config.yml darkalex17/managarr docker run --rm -it -v /home/aclarke/.config/managarr/config.yml:/root/.config/managarr/config.yml darkalex17/managarr:latest
``` ```
You can also clone this repo and run `make docker` to build a docker image locally and run it using the above command. You can also clone this repo and run `make docker` to build a docker image locally and run it using the above command.
Please note that you will need to create and popular your configuration file first before starting the container. Otherwise, the container will fail to start. Please note that you will need to create and popular your configuration file first before starting the container. Otherwise, the container will fail to start.
**Note:** If you run into errors using relative file paths when mounting the volume with the configuration file, try using an absolute path.
### Homebrew (Mac and Linux)
To install Managarr from Homebrew, install the Managarr tap and then you'll be able to install Managarr:
```shell
brew tap Dark-Alex-17/managarr
brew install managarr
# If you need to be more specific, use the following:
brew install Dark-Alex-17/managarr/managarr
```
To upgrade to a newer version of Managarr:
```shell
brew upgrade managarr
```
### Chocolatey (Windows)
The Managarr Chocolatey package is located [here](https://community.chocolatey.org/packages/managarr). Please note that validation
of Chocolatey packages take quite some time, and thus the package may not be available immediately after a new release.
```powershell
choco install managarr
# Some newer releases may require a version number, so you can specify it like so:
choco install managarr --version=0.4.1
```
To upgrade to the latest and greatest version of Managarr:
```powershell
choco upgrade managarr
# To upgrade to a specific version:
choco upgrade managarr --version=0.4.2
```
### Manual
Binaries are available on the [releases](https://github.com/Dark-Alex-17/managarr/releases) page for the following platforms:
| Platform | Architecture(s) |
|----------------|----------------------------|
| macOS | x86_64, arm64 |
| Linux GNU/MUSL | x86_64,armv6,armv7,aarch64 |
| Windows | x86_64,aarch64 |
#### Windows Instructions
To use a binary from the releases page on Windows, do the following:
1. Download the latest binary [binary](https://github.com/Dark-Alex-17/managarr/releases) for your OS.
2. Use 7-Zip or TarTool to unpack the Tar file.
3. Run the executable `managarr.exe`!
#### Linux/MacOS Instructions
To use a binary from the releases page on Linux/MacOS, do the following:
1. Download the latest binary [binary](https://github.com/Dark-Alex-17/managarr/releases) for your OS.
2. `cd` to the directory where you downloaded the binary.
3. Extract the binary with `tar -C /usr/local/bin -xzf managarr-<arch>.tar.gz` (NB: This may require `sudo`)
4. Now you can run `managarr`!
## Features ## Features
Key: Key:
@@ -88,22 +154,21 @@ Key:
| TUI | CLI | Feature | | TUI | CLI | Feature |
|-----|-----|--------------------------------------------------------------------------------------------------------------------| |-----|-----|--------------------------------------------------------------------------------------------------------------------|
| 🕒 | ✅ | View your library, downloads, blocklist, episodes | | | ✅ | View your library, downloads, blocklist, episodes |
| 🕒 | ✅ | View details of a specific series, or episode including description, history, downloaded file info, or the credits | | | ✅ | View details of a specific series, or episode including description, history, downloaded file info, or the credits |
| 🕒 | ✅ | View your host and security configs from the CLI to programmatically fetch the API token, among other settings | | 🚫 | ✅ | View your host and security configs from the CLI to programmatically fetch the API token, among other settings |
| 🕒 | ✅ | Search your library | | | ✅ | Search your library |
| 🕒 | ✅ | Add series to your library | | | ✅ | Add series to your library |
| 🕒 | ✅ | Delete series, downloads, indexers, root folders, and episode files | | | ✅ | Delete series, downloads, indexers, root folders, and episode files |
| 🕒 | ✅ | Mark history events as failed | | | ✅ | Trigger automatic searches for series, seasons, or episodes |
| 🕒 | ✅ | Trigger automatic searches for series, seasons, or episodes | | | ✅ | Trigger refresh and disk scan for series and downloads |
| 🕒 | ✅ | Trigger refresh and disk scan for series and downloads | | | ✅ | Manually search for series, seasons, or episodes |
| 🕒 | ✅ | Manually search for series, seasons, or episodes | | | ✅ | Edit your series and indexers |
| 🕒 | ✅ | Edit your series and indexers | | | ✅ | Manage your tags |
| 🕒 | ✅ | Manage your tags | | | ✅ | Manage your root folders |
| 🕒 | ✅ | Manage your root folders | | | ✅ | Manage your blocklist |
| 🕒 | ✅ | Manage your blocklist | | | ✅ | View and browse logs, tasks, events queues, and updates |
| 🕒 | ✅ | View and browse logs, tasks, events queues, and updates | | | ✅ | Manually trigger scheduled tasks |
| 🕒 | ✅ | Manually trigger scheduled tasks |
### Readarr ### Readarr
@@ -141,7 +206,7 @@ To see all available commands, simply run `managarr --help`:
```shell ```shell
$ managarr --help $ managarr --help
managarr 0.3.0 managarr 0.4.0
Alex Clarke <alex.j.tusa@gmail.com> Alex Clarke <alex.j.tusa@gmail.com>
A TUI and CLI to manage your Servarrs A TUI and CLI to manage your Servarrs
@@ -186,6 +251,8 @@ Commands:
start-task Start the specified Sonarr task start-task Start the specified Sonarr task
test-indexer Test the indexer with the given ID. Note that a successful test returns an empty JSON body; i.e. '{}' test-indexer Test the indexer with the given ID. Note that a successful test returns an empty JSON body; i.e. '{}'
test-all-indexers Test all Sonarr indexers test-all-indexers Test all Sonarr indexers
toggle-episode-monitoring Toggle monitoring for the specified episode
toggle-season-monitoring Toggle monitoring for the specified season that corresponds to the specified series ID
help Print this message or the help of the given subcommand(s) help Print this message or the help of the given subcommand(s)
Options: Options:
@@ -275,21 +342,28 @@ Managarr supports using environment variables on startup so you don't have to al
|-----------------------------------------|--------------------------------------------------------------------------------|----------------------------------| |-----------------------------------------|--------------------------------------------------------------------------------|----------------------------------|
| `MANAGARR_CONFIG_FILE` | Set the path to the config file | `--config` | | `MANAGARR_CONFIG_FILE` | Set the path to the config file | `--config` |
| `MANAGARR_DISABLE_SPINNER` | Disable the CLI spinner (this can be useful when scripting and parsing output) | `--disable-spinner` | | `MANAGARR_DISABLE_SPINNER` | Disable the CLI spinner (this can be useful when scripting and parsing output) | `--disable-spinner` |
|-----------------------------------------|--------------------------------------------------------------------------------|----------------------------------|
## Track My Progress for the Beta release (With Sonarr Support!) ## Track What I'm Currently Working On
Progress for the beta release can be followed on my [Wekan Board](https://wekan.alexjclarke.com/b/dHoGjBb44MHM9HSv4/managarr) To see what feature(s) I'm currently working on, check out my [Wekan Board](https://wekan.alexjclarke.com/b/dHoGjBb44MHM9HSv4/managarr).
with all items tagged `Beta`.
## Screenshots ## Screenshots
![library](screenshots/library.png) ### Radarr
![manual_search](screenshots/manual_search.png) ![radarr_library](screenshots/radarr/radarr_library.png)
![logs](screenshots/logs.png) ![manual_search](screenshots/radarr/manual_search.png)
![new_movie_search](screenshots/new_movie_search.png) ![new_movie_search](screenshots/radarr/new_movie_search.png)
![add_new_movie](screenshots/add_new_movie.png) ![add_new_movie](screenshots/radarr/add_new_movie.png)
![collection_details](screenshots/collection_details.png) ![collection_details](screenshots/radarr/collection_details.png)
![indexers](screenshots/indexers.png)
### Sonarr
![sonarr_library](screenshots/sonarr/sonarr_library.png)
![series_details](screenshots/sonarr/series_details.png)
![season_details](screenshots/sonarr/season_details.png)
![manual_episode_search](screenshots/sonarr/manual_episode_search.png)
### General
![logs](screenshots/radarr/logs.png)
![indexers](screenshots/radarr/indexers.png)
## Dependencies ## Dependencies
* [ratatui](https://github.com/tui-rs-revival/ratatui) * [ratatui](https://github.com/tui-rs-revival/ratatui)
@@ -301,7 +375,7 @@ with all items tagged `Beta`.
## Servarr Requirements ## Servarr Requirements
* [Radarr >= 5.3.6.8612](https://radarr.video/docs/api/) * [Radarr >= 5.3.6.8612](https://radarr.video/docs/api/)
* [Sonarr >= v3](https://sonarr.tv/docs/api/) * [Sonarr >= v4](https://sonarr.tv/docs/api/)
* [Readarr v1](https://readarr.com/docs/api/) * [Readarr v1](https://readarr.com/docs/api/)
* [Lidarr v1](https://lidarr.audio/docs/api/) * [Lidarr v1](https://lidarr.audio/docs/api/)
* [Whisparr >= v3](https://whisparr.com/docs/api/) * [Whisparr >= v3](https://whisparr.com/docs/api/)
@@ -0,0 +1,20 @@
$ErrorActionPreference = 'Stop';
$PackageName = 'managarr'
$toolsDir = "$(Split-Path -parent $MyInvocation.MyCommand.Definition)"
$url64 = 'https://github.com/Dark-Alex-17/managarr/releases/download/v$version/managarr-windows.tar.gz'
$checksum64 = '$hash_64'
$packageArgs = @{
packageName = $packageName
softwareName = $packageName
unzipLocation = $toolsDir
fileType = 'exe'
url = $url64
checksum = $checksum64
checksumType = 'sha256'
}
Install-ChocolateyZipPackage @packageArgs
$File = Get-ChildItem -File -Path $env:ChocolateyInstall\lib\$packageName\tools\ -Filter *.tar
Get-ChocolateyUnzip -fileFullPath $File.FullName -destination $env:ChocolateyInstall\lib\$packageName\tools\
@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Read this before creating packages: https://chocolatey.org/docs/create-packages -->
<!-- It is especially important to read the above link to understand additional requirements when publishing packages to the community feed aka dot org (https://chocolatey.org/packages). -->
<!-- Test your packages in a test environment: https://github.com/chocolatey/chocolatey-test-environment -->
<!--
This is a nuspec. It mostly adheres to https://docs.nuget.org/create/Nuspec-Reference. Chocolatey uses a special version of NuGet.Core that allows us to do more than was initially possible. As such there are certain things to be aware of:
* the package xmlns schema url may cause issues with nuget.exe
* Any of the following elements can ONLY be used by choco tools - projectSourceUrl, docsUrl, mailingListUrl, bugTrackerUrl, packageSourceUrl, provides, conflicts, replaces
* nuget.exe can still install packages with those elements but they are ignored. Any authoring tools or commands will error on those elements
-->
<!-- You can embed software files directly into packages, as long as you are not bound by distribution rights. -->
<!-- * If you are an organization making private packages, you probably have no issues here -->
<!-- * If you are releasing to the community feed, you need to consider distribution rights. -->
<!-- Do not remove this test for UTF-8: if “Ω” doesnt appear as greek uppercase omega letter enclosed in quotation marks, you should use an editor that supports UTF-8, not this one. -->
<package xmlns="http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd">
<metadata>
<!-- == PACKAGE SPECIFIC SECTION == -->
<id>managarr</id>
<version>$version</version>
<!-- == SOFTWARE SPECIFIC SECTION == -->
<!-- This section is about the software itself -->
<title>Managarr</title>
<authors>Alex Clarke</authors>
<projectUrl>https://github.com/Dark-Alex-17/managarr</projectUrl>
<licenseUrl>https://github.com/Dark-Alex-17/managarr/blob/main/LICENSE</licenseUrl>
<requireLicenseAcceptance>true</requireLicenseAcceptance>
<projectSourceUrl>https://github.com/Dark-Alex-17/managarr</projectSourceUrl>
<docsUrl>https://github.com/Dark-Alex-17/managarr/blob/main/README.md</docsUrl>
<bugTrackerUrl>https://github.com/Dark-Alex-17/managarr/issues</bugTrackerUrl>
<tags>cli cross-platform terminal servarr tui sonarr radarr rust</tags>
<summary>A TUI and CLI for managing *arr servers.</summary>
<description>
A TUI and CLI for managing *arr servers. Build with love in Rust!
**Usage**
To use, run `managarr` in a terminal.
For more [documentation and usage](https://github.com/Dark-Alex-17/managarr/blob/main/README.md), see the [official repo](https://github.com/Dark-Alex-17/managarr).
</description>
<releaseNotes>https://github.com/Dark-Alex-17/managarr/releases/tag/v$version/</releaseNotes>
</metadata>
<files>
<!-- this section controls what actually gets packaged into the Chocolatey package -->
<file src="tools\**" target="tools" />
<!--Building from Linux? You may need this instead: <file src="tools/**" target="tools" />-->
</files>
</package>
+28
View File
@@ -0,0 +1,28 @@
import hashlib
import sys
from string import Template
sys.stdout.reconfigure(encoding='utf-8')
args = sys.argv
version = args[1].replace("v", "")
template_file_path = args[2]
generated_file_path = args[3]
# Deployment files
hash_64 = args[4].strip()
print("Generating formula")
print(" VERSION: %s" % version)
print(" TEMPLATE PATH: %s" % template_file_path)
print(" SAVING AT: %s" % generated_file_path)
print(" HASH: %s" % hash_64)
with open(template_file_path, "r", encoding="utf-8") as template_file:
template = Template(template_file.read())
substitute = template.safe_substitute(version=version, hash_64=hash_64)
print("\n================== Generated package file ==================\n")
print(substitute)
print("\n============================================================\n")
with open(generated_file_path, "w", encoding="utf-8") as generated_file:
generated_file.write(substitute)
+24
View File
@@ -0,0 +1,24 @@
# Documentation: https://docs.brew.sh/Formula-Cookbook
# https://rubydoc.brew.sh/Formula
class Managarr < Formula
desc "A fast and simple dashboard for Kubernetes written in Rust"
homepage "https://github.com/Dark-Alex-17/managarr"
if OS.mac? and Hardware::CPU.arm?
url "https://github.com/Dark-Alex-17/managarr/releases/download/v$version/managarr-macos-arm64.tar.gz"
sha256 "$hash_mac_arm"
elsif OS.mac? and Hardware::CPU.intel?
url "https://github.com/Dark-Alex-17/managarr/releases/download/v$version/managarr-macos.tar.gz"
sha256 "$hash_mac"
else
url "https://github.com/Dark-Alex-17/managarr/releases/download/v$version/managarr-linux-musl.tar.gz"
sha256 "$hash_linux"
end
version "$version"
license "MIT"
def install
bin.install "managarr"
ohai "You're done! Run with \"managarr\""
ohai "For runtime flags, see \"managarr --help\""
end
end
+31
View File
@@ -0,0 +1,31 @@
import hashlib
import sys
from string import Template
args = sys.argv
version = args[1]
template_file_path = args[2]
generated_file_path = args[3]
# Deployment files
hash_mac = args[4].strip()
hash_mac_arm = args[5].strip()
hash_linux = args[6].strip()
print("Generating formula")
print(" VERSION: %s" % version)
print(" TEMPLATE PATH: %s" % template_file_path)
print(" SAVING AT: %s" % generated_file_path)
print(" MAC HASH: %s" % hash_mac)
print(" MAC ARM HASH: %s" % hash_mac_arm)
print(" LINUX HASH: %s" % hash_linux)
with open(template_file_path, "r") as template_file:
template = Template(template_file.read())
substitute = template.safe_substitute(version=version, hash_mac=hash_mac, hash_mac_arm=hash_mac_arm, hash_linux=hash_linux)
print("\n================== Generated package file ==================\n")
print(substitute)
print("\n============================================================\n")
with open(generated_file_path, "w") as generated_file:
generated_file.write(substitute)

Before

Width:  |  Height:  |  Size: 152 KiB

After

Width:  |  Height:  |  Size: 152 KiB

Before

Width:  |  Height:  |  Size: 154 KiB

After

Width:  |  Height:  |  Size: 154 KiB

Before

Width:  |  Height:  |  Size: 142 KiB

After

Width:  |  Height:  |  Size: 142 KiB

Before

Width:  |  Height:  |  Size: 374 KiB

After

Width:  |  Height:  |  Size: 374 KiB

Before

Width:  |  Height:  |  Size: 382 KiB

After

Width:  |  Height:  |  Size: 382 KiB

Before

Width:  |  Height:  |  Size: 176 KiB

After

Width:  |  Height:  |  Size: 176 KiB

Before

Width:  |  Height:  |  Size: 236 KiB

After

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

+99 -24
View File
@@ -1,24 +1,84 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::models::Route;
use anyhow::anyhow; use anyhow::anyhow;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use rstest::rstest;
use tokio::sync::mpsc; use tokio::sync::mpsc;
use crate::app::context_clues::{build_context_clue_string, SERVARR_CONTEXT_CLUES}; use crate::app::context_clues::{build_context_clue_string, SERVARR_CONTEXT_CLUES};
use crate::app::{App, AppConfig, Data, ServarrConfig, DEFAULT_ROUTE}; use crate::app::{App, AppConfig, Data, ServarrConfig};
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, RadarrData}; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, RadarrData};
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, SonarrData}; use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, SonarrData};
use crate::models::{HorizontallyScrollableText, TabRoute}; use crate::models::{HorizontallyScrollableText, TabRoute};
use crate::network::radarr_network::RadarrEvent; use crate::network::radarr_network::RadarrEvent;
use crate::network::NetworkEvent; use crate::network::NetworkEvent;
use tokio_util::sync::CancellationToken;
#[rstest]
fn test_app_new(
#[values(ActiveRadarrBlock::default(), ActiveSonarrBlock::default())] servarr: impl Into<Route>
+ Copy,
) {
let (title, config) = match servarr.into() {
Route::Radarr(_, _) => (
"Radarr",
AppConfig {
radarr: Some(ServarrConfig::default()),
..AppConfig::default()
},
),
Route::Sonarr(_, _) => (
"Sonarr",
AppConfig {
sonarr: Some(ServarrConfig::default()),
..AppConfig::default()
},
),
_ => unreachable!(),
};
let tab_route = |title: &'static str| TabRoute {
title,
route: servarr.into(),
help: format!(
"<↑↓> scroll | ←→ change tab | {} ",
build_context_clue_string(&SERVARR_CONTEXT_CLUES)
),
contextual_help: None,
};
let app = App::new(
mpsc::channel::<NetworkEvent>(500).0,
config,
CancellationToken::new(),
);
assert!(app.navigation_stack.is_empty());
assert_eq!(app.get_current_route(), servarr.into());
assert!(app.network_tx.is_some());
assert!(!app.cancellation_token.is_cancelled());
assert!(app.is_first_render);
assert_eq!(app.error, HorizontallyScrollableText::default());
assert_eq!(app.server_tabs.index, 0);
assert_eq!(app.server_tabs.tabs, vec![tab_route(title)]);
assert_eq!(app.tick_until_poll, 400);
assert_eq!(app.ticks_until_scroll, 4);
assert_eq!(app.tick_count, 0);
assert!(!app.is_loading);
assert!(!app.is_routing);
assert!(!app.should_refresh);
assert!(!app.should_ignore_quit_key);
assert!(!app.cli_mode);
}
#[test] #[test]
fn test_app_default() { fn test_app_default() {
let app = App::default(); let app = App::default();
assert_eq!(app.navigation_stack, vec![DEFAULT_ROUTE]); assert!(app.navigation_stack.is_empty());
assert!(app.network_tx.is_none()); assert!(app.network_tx.is_none());
assert!(!app.cancellation_token.is_cancelled()); assert!(!app.cancellation_token.is_cancelled());
assert!(app.is_first_render);
assert_eq!(app.error, HorizontallyScrollableText::default()); assert_eq!(app.error, HorizontallyScrollableText::default());
assert_eq!(app.server_tabs.index, 0); assert_eq!(app.server_tabs.index, 0);
assert_eq!( assert_eq!(
@@ -36,7 +96,10 @@ mod tests {
TabRoute { TabRoute {
title: "Sonarr", title: "Sonarr",
route: ActiveSonarrBlock::Series.into(), route: ActiveSonarrBlock::Series.into(),
help: format!("{} ", build_context_clue_string(&SERVARR_CONTEXT_CLUES)), help: format!(
"<↑↓> scroll | ←→ change tab | {} ",
build_context_clue_string(&SERVARR_CONTEXT_CLUES)
),
contextual_help: None, contextual_help: None,
}, },
] ]
@@ -54,15 +117,13 @@ mod tests {
#[test] #[test]
fn test_navigation_stack_methods() { fn test_navigation_stack_methods() {
let mut app = App::default(); let mut app = App::default();
let default_route = app.server_tabs.tabs.first().unwrap().route;
assert_eq!(app.get_current_route(), &DEFAULT_ROUTE); assert_eq!(app.get_current_route(), default_route);
app.push_navigation_stack(ActiveRadarrBlock::Downloads.into()); app.push_navigation_stack(ActiveRadarrBlock::Downloads.into());
assert_eq!( assert_eq!(app.get_current_route(), ActiveRadarrBlock::Downloads.into());
app.get_current_route(),
&ActiveRadarrBlock::Downloads.into()
);
assert!(app.is_routing); assert!(app.is_routing);
app.is_routing = false; app.is_routing = false;
@@ -70,20 +131,20 @@ mod tests {
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
&ActiveRadarrBlock::Collections.into() ActiveRadarrBlock::Collections.into()
); );
assert!(app.is_routing); assert!(app.is_routing);
app.is_routing = false; app.is_routing = false;
app.pop_navigation_stack(); app.pop_navigation_stack();
assert_eq!(app.get_current_route(), &DEFAULT_ROUTE); assert_eq!(app.get_current_route(), default_route);
assert!(app.is_routing); assert!(app.is_routing);
app.is_routing = false; app.is_routing = false;
app.pop_navigation_stack(); app.pop_navigation_stack();
assert_eq!(app.get_current_route(), &DEFAULT_ROUTE); assert_eq!(app.get_current_route(), default_route);
assert!(app.is_routing); assert!(app.is_routing);
} }
@@ -120,19 +181,23 @@ mod tests {
#[test] #[test]
fn test_reset() { fn test_reset() {
let radarr_data = RadarrData {
version: "test".into(),
..RadarrData::default()
};
let sonarr_data = SonarrData {
version: "test".into(),
..SonarrData::default()
};
let data = Data {
radarr_data,
sonarr_data,
};
let mut app = App { let mut app = App {
tick_count: 2, tick_count: 2,
error: "Test error".to_owned().into(), error: "Test error".to_owned().into(),
data: Data { is_first_render: false,
radarr_data: RadarrData { data,
version: "test".to_owned(),
..RadarrData::default()
},
sonarr_data: SonarrData {
version: "test".to_owned(),
..SonarrData::default()
},
},
..App::default() ..App::default()
}; };
@@ -140,6 +205,7 @@ mod tests {
assert_eq!(app.tick_count, 0); assert_eq!(app.tick_count, 0);
assert_eq!(app.error, HorizontallyScrollableText::default()); assert_eq!(app.error, HorizontallyScrollableText::default());
assert!(app.is_first_render);
assert!(app.data.radarr_data.version.is_empty()); assert!(app.data.radarr_data.version.is_empty());
assert!(app.data.sonarr_data.version.is_empty()); assert!(app.data.sonarr_data.version.is_empty());
} }
@@ -188,12 +254,13 @@ mod tests {
let mut app = App { let mut app = App {
tick_until_poll: 2, tick_until_poll: 2,
network_tx: Some(sync_network_tx), network_tx: Some(sync_network_tx),
is_first_render: true,
..App::default() ..App::default()
}; };
assert_eq!(app.tick_count, 0); assert_eq!(app.tick_count, 0);
app.on_tick(true).await; app.on_tick().await;
assert_eq!( assert_eq!(
sync_network_rx.recv().await.unwrap(), sync_network_rx.recv().await.unwrap(),
@@ -219,6 +286,14 @@ mod tests {
sync_network_rx.recv().await.unwrap(), sync_network_rx.recv().await.unwrap(),
RadarrEvent::GetStatus.into() RadarrEvent::GetStatus.into()
); );
assert_eq!(
sync_network_rx.recv().await.unwrap(),
RadarrEvent::GetQualityProfiles.into()
);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
RadarrEvent::GetTags.into()
);
assert_eq!( assert_eq!(
sync_network_rx.recv().await.unwrap(), sync_network_rx.recv().await.unwrap(),
RadarrEvent::GetMovies.into() RadarrEvent::GetMovies.into()
@@ -237,7 +312,7 @@ mod tests {
..App::default() ..App::default()
}; };
app.on_tick(false).await; app.on_tick().await;
assert!(!app.is_routing); assert!(!app.is_routing);
} }
@@ -250,7 +325,7 @@ mod tests {
..App::default() ..App::default()
}; };
app.on_tick(false).await; app.on_tick().await;
assert!(!app.should_refresh); assert!(!app.should_refresh);
} }
+69 -2
View File
@@ -14,10 +14,77 @@ pub fn build_context_clue_string(context_clues: &[(KeyBinding, &str)]) -> String
.join(" | ") .join(" | ")
} }
pub static SERVARR_CONTEXT_CLUES: [ContextClue; 2] = [ pub static SERVARR_CONTEXT_CLUES: [ContextClue; 3] = [
(DEFAULT_KEYBINDINGS.tab, "change servarr"), (
DEFAULT_KEYBINDINGS.next_servarr,
DEFAULT_KEYBINDINGS.next_servarr.desc,
),
(
DEFAULT_KEYBINDINGS.previous_servarr,
DEFAULT_KEYBINDINGS.previous_servarr.desc,
),
(DEFAULT_KEYBINDINGS.quit, DEFAULT_KEYBINDINGS.quit.desc), (DEFAULT_KEYBINDINGS.quit, DEFAULT_KEYBINDINGS.quit.desc),
]; ];
pub static BARE_POPUP_CONTEXT_CLUES: [ContextClue; 1] = pub static BARE_POPUP_CONTEXT_CLUES: [ContextClue; 1] =
[(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc)]; [(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc)];
pub static BLOCKLIST_CONTEXT_CLUES: [ContextClue; 5] = [
(
DEFAULT_KEYBINDINGS.refresh,
DEFAULT_KEYBINDINGS.refresh.desc,
),
(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc),
(DEFAULT_KEYBINDINGS.submit, "details"),
(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc),
(DEFAULT_KEYBINDINGS.clear, "clear blocklist"),
];
pub static CONFIRMATION_PROMPT_CONTEXT_CLUES: [ContextClue; 2] = [
(DEFAULT_KEYBINDINGS.confirm, "submit"),
(DEFAULT_KEYBINDINGS.esc, "cancel"),
];
pub static DOWNLOADS_CONTEXT_CLUES: [ContextClue; 3] = [
(
DEFAULT_KEYBINDINGS.refresh,
DEFAULT_KEYBINDINGS.refresh.desc,
),
(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc),
(DEFAULT_KEYBINDINGS.update, "update downloads"),
];
pub static ROOT_FOLDERS_CONTEXT_CLUES: [ContextClue; 3] = [
(DEFAULT_KEYBINDINGS.add, DEFAULT_KEYBINDINGS.add.desc),
(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc),
(
DEFAULT_KEYBINDINGS.refresh,
DEFAULT_KEYBINDINGS.refresh.desc,
),
];
pub static INDEXERS_CONTEXT_CLUES: [ContextClue; 6] = [
(DEFAULT_KEYBINDINGS.submit, "edit indexer"),
(
DEFAULT_KEYBINDINGS.settings,
DEFAULT_KEYBINDINGS.settings.desc,
),
(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc),
(DEFAULT_KEYBINDINGS.test, "test indexer"),
(DEFAULT_KEYBINDINGS.test_all, "test all indexers"),
(
DEFAULT_KEYBINDINGS.refresh,
DEFAULT_KEYBINDINGS.refresh.desc,
),
];
pub static SYSTEM_CONTEXT_CLUES: [ContextClue; 5] = [
(DEFAULT_KEYBINDINGS.tasks, "open tasks"),
(DEFAULT_KEYBINDINGS.events, "open events"),
(DEFAULT_KEYBINDINGS.logs, "open logs"),
(DEFAULT_KEYBINDINGS.update, "open updates"),
(
DEFAULT_KEYBINDINGS.refresh,
DEFAULT_KEYBINDINGS.refresh.desc,
),
];
+168 -3
View File
@@ -2,7 +2,11 @@
mod test { mod test {
use pretty_assertions::{assert_eq, assert_str_eq}; use pretty_assertions::{assert_eq, assert_str_eq};
use crate::app::context_clues::{BARE_POPUP_CONTEXT_CLUES, SERVARR_CONTEXT_CLUES}; use crate::app::context_clues::{
BARE_POPUP_CONTEXT_CLUES, BLOCKLIST_CONTEXT_CLUES, CONFIRMATION_PROMPT_CONTEXT_CLUES,
DOWNLOADS_CONTEXT_CLUES, INDEXERS_CONTEXT_CLUES, ROOT_FOLDERS_CONTEXT_CLUES,
SERVARR_CONTEXT_CLUES, SYSTEM_CONTEXT_CLUES,
};
use crate::app::{context_clues::build_context_clue_string, key_binding::DEFAULT_KEYBINDINGS}; use crate::app::{context_clues::build_context_clue_string, key_binding::DEFAULT_KEYBINDINGS};
#[test] #[test]
@@ -24,8 +28,13 @@ mod test {
let (key_binding, description) = servarr_context_clues_iter.next().unwrap(); let (key_binding, description) = servarr_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.tab); assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.next_servarr);
assert_str_eq!(*description, "change servarr"); assert_str_eq!(*description, DEFAULT_KEYBINDINGS.next_servarr.desc);
let (key_binding, description) = servarr_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.previous_servarr);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.previous_servarr.desc);
let (key_binding, description) = servarr_context_clues_iter.next().unwrap(); let (key_binding, description) = servarr_context_clues_iter.next().unwrap();
@@ -44,4 +53,160 @@ mod test {
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.esc.desc); assert_str_eq!(*description, DEFAULT_KEYBINDINGS.esc.desc);
assert_eq!(bare_popup_context_clues_iter.next(), None); assert_eq!(bare_popup_context_clues_iter.next(), None);
} }
#[test]
fn test_downloads_context_clues() {
let mut downloads_context_clues_iter = DOWNLOADS_CONTEXT_CLUES.iter();
let (key_binding, description) = downloads_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
let (key_binding, description) = downloads_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.delete);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.delete.desc);
let (key_binding, description) = downloads_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.update);
assert_str_eq!(*description, "update downloads");
assert_eq!(downloads_context_clues_iter.next(), None);
}
#[test]
fn test_blocklist_context_clues() {
let mut blocklist_context_clues_iter = BLOCKLIST_CONTEXT_CLUES.iter();
let (key_binding, description) = blocklist_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
let (key_binding, description) = blocklist_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.sort);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.sort.desc);
let (key_binding, description) = blocklist_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit);
assert_str_eq!(*description, "details");
let (key_binding, description) = blocklist_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.delete);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.delete.desc);
let (key_binding, description) = blocklist_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.clear);
assert_str_eq!(*description, "clear blocklist");
assert_eq!(blocklist_context_clues_iter.next(), None);
}
#[test]
fn test_confirmation_prompt_context_clues() {
let mut confirmation_prompt_context_clues_iter = CONFIRMATION_PROMPT_CONTEXT_CLUES.iter();
let (key_binding, description) = confirmation_prompt_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.confirm);
assert_str_eq!(*description, "submit");
let (key_binding, description) = confirmation_prompt_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
assert_str_eq!(*description, "cancel");
assert_eq!(confirmation_prompt_context_clues_iter.next(), None);
}
#[test]
fn test_root_folders_context_clues() {
let mut root_folders_context_clues_iter = ROOT_FOLDERS_CONTEXT_CLUES.iter();
let (key_binding, description) = root_folders_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.add);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.add.desc);
let (key_binding, description) = root_folders_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.delete);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.delete.desc);
let (key_binding, description) = root_folders_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
assert_eq!(root_folders_context_clues_iter.next(), None);
}
#[test]
fn test_indexers_context_clues() {
let mut indexers_context_clues_iter = INDEXERS_CONTEXT_CLUES.iter();
let (key_binding, description) = indexers_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit);
assert_str_eq!(*description, "edit indexer");
let (key_binding, description) = indexers_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.settings);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.settings.desc);
let (key_binding, description) = indexers_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.delete);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.delete.desc);
let (key_binding, description) = indexers_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.test);
assert_str_eq!(*description, "test indexer");
let (key_binding, description) = indexers_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.test_all);
assert_str_eq!(*description, "test all indexers");
let (key_binding, description) = indexers_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
assert_eq!(indexers_context_clues_iter.next(), None);
}
#[test]
fn test_system_context_clues() {
let mut system_context_clues_iter = SYSTEM_CONTEXT_CLUES.iter();
let (key_binding, description) = system_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.tasks);
assert_str_eq!(*description, "open tasks");
let (key_binding, description) = system_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.events);
assert_str_eq!(*description, "open events");
let (key_binding, description) = system_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.logs);
assert_str_eq!(*description, "open logs");
let (key_binding, description) = system_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.update);
assert_str_eq!(*description, "open updates");
let (key_binding, description) = system_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
assert_eq!(system_context_clues_iter.next(), None);
}
} }
+21 -6
View File
@@ -15,8 +15,11 @@ generate_keybindings! {
left, left,
right, right,
backspace, backspace,
next_servarr,
previous_servarr,
clear, clear,
search, search,
auto_search,
settings, settings,
filter, filter,
sort, sort,
@@ -25,12 +28,12 @@ generate_keybindings! {
tasks, tasks,
test, test,
test_all, test_all,
toggle_monitoring,
refresh, refresh,
update, update,
events, events,
home, home,
end, end,
tab,
delete, delete,
submit, submit,
confirm, confirm,
@@ -69,16 +72,28 @@ pub const DEFAULT_KEYBINDINGS: KeyBindings = KeyBindings {
key: Key::Backspace, key: Key::Backspace,
desc: "backspace", desc: "backspace",
}, },
next_servarr: KeyBinding {
key: Key::Tab,
desc: "next servarr",
},
previous_servarr: KeyBinding {
key: Key::BackTab,
desc: "previous servarr",
},
clear: KeyBinding { clear: KeyBinding {
key: Key::Char('c'), key: Key::Char('c'),
desc: "clear", desc: "clear",
}, },
auto_search: KeyBinding {
key: Key::Char('S'),
desc: "auto search",
},
search: KeyBinding { search: KeyBinding {
key: Key::Char('s'), key: Key::Char('s'),
desc: "search", desc: "search",
}, },
settings: KeyBinding { settings: KeyBinding {
key: Key::Char('s'), key: Key::Char('S'),
desc: "settings", desc: "settings",
}, },
filter: KeyBinding { filter: KeyBinding {
@@ -113,6 +128,10 @@ pub const DEFAULT_KEYBINDINGS: KeyBindings = KeyBindings {
key: Key::Char('T'), key: Key::Char('T'),
desc: "test all", desc: "test all",
}, },
toggle_monitoring: KeyBinding {
key: Key::Char('m'),
desc: "toggle monitoring",
},
refresh: KeyBinding { refresh: KeyBinding {
key: Key::Ctrl('r'), key: Key::Ctrl('r'),
desc: "refresh", desc: "refresh",
@@ -129,10 +148,6 @@ pub const DEFAULT_KEYBINDINGS: KeyBindings = KeyBindings {
key: Key::End, key: Key::End,
desc: "end", desc: "end",
}, },
tab: KeyBinding {
key: Key::Tab,
desc: "tab",
},
delete: KeyBinding { delete: KeyBinding {
key: Key::Delete, key: Key::Delete,
desc: "delete", desc: "delete",
+5 -2
View File
@@ -13,9 +13,12 @@ mod test {
#[case(DEFAULT_KEYBINDINGS.left, Key::Left, "left")] #[case(DEFAULT_KEYBINDINGS.left, Key::Left, "left")]
#[case(DEFAULT_KEYBINDINGS.right, Key::Right, "right")] #[case(DEFAULT_KEYBINDINGS.right, Key::Right, "right")]
#[case(DEFAULT_KEYBINDINGS.backspace, Key::Backspace, "backspace")] #[case(DEFAULT_KEYBINDINGS.backspace, Key::Backspace, "backspace")]
#[case(DEFAULT_KEYBINDINGS.next_servarr, Key::Tab, "next servarr")]
#[case(DEFAULT_KEYBINDINGS.previous_servarr, Key::BackTab, "previous servarr")]
#[case(DEFAULT_KEYBINDINGS.clear, Key::Char('c'), "clear")] #[case(DEFAULT_KEYBINDINGS.clear, Key::Char('c'), "clear")]
#[case(DEFAULT_KEYBINDINGS.auto_search, Key::Char('S'), "auto search")]
#[case(DEFAULT_KEYBINDINGS.search, Key::Char('s'), "search")] #[case(DEFAULT_KEYBINDINGS.search, Key::Char('s'), "search")]
#[case(DEFAULT_KEYBINDINGS.settings, Key::Char('s'), "settings")] #[case(DEFAULT_KEYBINDINGS.settings, Key::Char('S'), "settings")]
#[case(DEFAULT_KEYBINDINGS.filter, Key::Char('f'), "filter")] #[case(DEFAULT_KEYBINDINGS.filter, Key::Char('f'), "filter")]
#[case(DEFAULT_KEYBINDINGS.sort, Key::Char('o'), "sort")] #[case(DEFAULT_KEYBINDINGS.sort, Key::Char('o'), "sort")]
#[case(DEFAULT_KEYBINDINGS.edit, Key::Char('e'), "edit")] #[case(DEFAULT_KEYBINDINGS.edit, Key::Char('e'), "edit")]
@@ -24,11 +27,11 @@ mod test {
#[case(DEFAULT_KEYBINDINGS.tasks, Key::Char('t'), "tasks")] #[case(DEFAULT_KEYBINDINGS.tasks, Key::Char('t'), "tasks")]
#[case(DEFAULT_KEYBINDINGS.test, Key::Char('t'), "test")] #[case(DEFAULT_KEYBINDINGS.test, Key::Char('t'), "test")]
#[case(DEFAULT_KEYBINDINGS.test_all, Key::Char('T'), "test all")] #[case(DEFAULT_KEYBINDINGS.test_all, Key::Char('T'), "test all")]
#[case(DEFAULT_KEYBINDINGS.toggle_monitoring, Key::Char('m'), "toggle monitoring")]
#[case(DEFAULT_KEYBINDINGS.refresh, Key::Ctrl('r'), "refresh")] #[case(DEFAULT_KEYBINDINGS.refresh, Key::Ctrl('r'), "refresh")]
#[case(DEFAULT_KEYBINDINGS.update, Key::Char('u'), "update")] #[case(DEFAULT_KEYBINDINGS.update, Key::Char('u'), "update")]
#[case(DEFAULT_KEYBINDINGS.home, Key::Home, "home")] #[case(DEFAULT_KEYBINDINGS.home, Key::Home, "home")]
#[case(DEFAULT_KEYBINDINGS.end, Key::End, "end")] #[case(DEFAULT_KEYBINDINGS.end, Key::End, "end")]
#[case(DEFAULT_KEYBINDINGS.tab, Key::Tab, "tab")]
#[case(DEFAULT_KEYBINDINGS.delete, Key::Delete, "delete")] #[case(DEFAULT_KEYBINDINGS.delete, Key::Delete, "delete")]
#[case(DEFAULT_KEYBINDINGS.submit, Key::Enter, "submit")] #[case(DEFAULT_KEYBINDINGS.submit, Key::Enter, "submit")]
#[case(DEFAULT_KEYBINDINGS.confirm, Key::Ctrl('s'), "submit")] #[case(DEFAULT_KEYBINDINGS.confirm, Key::Ctrl('s'), "submit")]
+58 -17
View File
@@ -1,6 +1,6 @@
use std::process; use std::process;
use anyhow::anyhow; use anyhow::{anyhow, Error};
use colored::Colorize; use colored::Colorize;
use log::{debug, error}; use log::{debug, error};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -21,13 +21,13 @@ pub mod context_clues;
pub mod key_binding; pub mod key_binding;
mod key_binding_tests; mod key_binding_tests;
pub mod radarr; pub mod radarr;
pub mod sonarr;
const DEFAULT_ROUTE: Route = Route::Radarr(ActiveRadarrBlock::Movies, None);
pub struct App<'a> { pub struct App<'a> {
navigation_stack: Vec<Route>, navigation_stack: Vec<Route>,
network_tx: Option<Sender<NetworkEvent>>, network_tx: Option<Sender<NetworkEvent>>,
cancellation_token: CancellationToken, pub cancellation_token: CancellationToken,
pub is_first_render: bool,
pub server_tabs: TabState, pub server_tabs: TabState,
pub error: HorizontallyScrollableText, pub error: HorizontallyScrollableText,
pub tick_until_poll: u64, pub tick_until_poll: u64,
@@ -48,10 +48,37 @@ impl<'a> App<'a> {
config: AppConfig, config: AppConfig,
cancellation_token: CancellationToken, cancellation_token: CancellationToken,
) -> Self { ) -> Self {
let mut server_tabs = Vec::new();
if config.radarr.is_some() {
server_tabs.push(TabRoute {
title: "Radarr",
route: ActiveRadarrBlock::Movies.into(),
help: format!(
"<↑↓> scroll | ←→ change tab | {} ",
build_context_clue_string(&SERVARR_CONTEXT_CLUES)
),
contextual_help: None,
});
}
if config.sonarr.is_some() {
server_tabs.push(TabRoute {
title: "Sonarr",
route: ActiveSonarrBlock::Series.into(),
help: format!(
"<↑↓> scroll | ←→ change tab | {} ",
build_context_clue_string(&SERVARR_CONTEXT_CLUES)
),
contextual_help: None,
});
}
App { App {
network_tx: Some(network_tx), network_tx: Some(network_tx),
config, config,
cancellation_token, cancellation_token,
server_tabs: TabState::new(server_tabs),
..App::default() ..App::default()
} }
} }
@@ -76,26 +103,26 @@ impl<'a> App<'a> {
self.tick_count = 0; self.tick_count = 0;
} }
// Allowing this code for now since we'll eventually be implementing additional Servarr support and we'll need it then
#[allow(dead_code)] #[allow(dead_code)]
pub fn reset(&mut self) { pub fn reset(&mut self) {
self.reset_tick_count(); self.reset_tick_count();
self.error = HorizontallyScrollableText::default(); self.error = HorizontallyScrollableText::default();
self.is_first_render = true;
self.data = Data::default(); self.data = Data::default();
} }
pub fn handle_error(&mut self, error: anyhow::Error) { pub fn handle_error(&mut self, error: Error) {
if self.error.text.is_empty() { if self.error.text.is_empty() {
self.error = error.to_string().into(); self.error = error.to_string().into();
} }
} }
pub async fn on_tick(&mut self, is_first_render: bool) { pub async fn on_tick(&mut self) {
if self.tick_count % self.tick_until_poll == 0 || self.is_routing || self.should_refresh { if self.tick_count % self.tick_until_poll == 0 || self.is_routing || self.should_refresh {
if let Route::Radarr(active_radarr_block, _) = self.get_current_route() { match self.get_current_route() {
self Route::Radarr(active_radarr_block, _) => self.radarr_on_tick(active_radarr_block).await,
.radarr_on_tick(*active_radarr_block, is_first_render) Route::Sonarr(active_sonarr_block, _) => self.sonarr_on_tick(active_sonarr_block).await,
.await; _ => (),
} }
self.is_routing = false; self.is_routing = false;
@@ -112,7 +139,7 @@ impl<'a> App<'a> {
pub fn pop_navigation_stack(&mut self) { pub fn pop_navigation_stack(&mut self) {
self.is_routing = true; self.is_routing = true;
if self.navigation_stack.len() > 1 { if !self.navigation_stack.is_empty() {
self.navigation_stack.pop(); self.navigation_stack.pop();
} }
} }
@@ -130,18 +157,22 @@ impl<'a> App<'a> {
self.push_navigation_stack(route); self.push_navigation_stack(route);
} }
pub fn get_current_route(&self) -> &Route { pub fn get_current_route(&self) -> Route {
self.navigation_stack.last().unwrap_or(&DEFAULT_ROUTE) *self
.navigation_stack
.last()
.unwrap_or(&self.server_tabs.tabs.first().unwrap().route)
} }
} }
impl<'a> Default for App<'a> { impl<'a> Default for App<'a> {
fn default() -> Self { fn default() -> Self {
App { App {
navigation_stack: vec![DEFAULT_ROUTE], navigation_stack: Vec::new(),
network_tx: None, network_tx: None,
cancellation_token: CancellationToken::new(), cancellation_token: CancellationToken::new(),
error: HorizontallyScrollableText::default(), error: HorizontallyScrollableText::default(),
is_first_render: true,
server_tabs: TabState::new(vec![ server_tabs: TabState::new(vec![
TabRoute { TabRoute {
title: "Radarr", title: "Radarr",
@@ -155,7 +186,10 @@ impl<'a> Default for App<'a> {
TabRoute { TabRoute {
title: "Sonarr", title: "Sonarr",
route: ActiveSonarrBlock::Series.into(), route: ActiveSonarrBlock::Series.into(),
help: format!("{} ", build_context_clue_string(&SERVARR_CONTEXT_CLUES)), help: format!(
"<↑↓> scroll | ←→ change tab | {} ",
build_context_clue_string(&SERVARR_CONTEXT_CLUES)
),
contextual_help: None, contextual_help: None,
}, },
]), ]),
@@ -176,7 +210,7 @@ impl<'a> Default for App<'a> {
#[derive(Default)] #[derive(Default)]
pub struct Data<'a> { pub struct Data<'a> {
pub radarr_data: RadarrData<'a>, pub radarr_data: RadarrData<'a>,
pub sonarr_data: SonarrData, pub sonarr_data: SonarrData<'a>,
} }
#[derive(Debug, Deserialize, Serialize, Default, Clone)] #[derive(Debug, Deserialize, Serialize, Default, Clone)]
@@ -187,6 +221,13 @@ pub struct AppConfig {
impl AppConfig { impl AppConfig {
pub fn validate(&self) { pub fn validate(&self) {
if self.radarr.is_none() && self.sonarr.is_none() {
log_and_print_error(
"No Servarr configuration provided in the specified configuration file".to_owned(),
);
process::exit(1);
}
if let Some(radarr_config) = &self.radarr { if let Some(radarr_config) = &self.radarr {
radarr_config.validate(); radarr_config.validate();
} }
+73 -20
View File
@@ -17,11 +17,23 @@ impl<'a> App<'a> {
.await; .await;
} }
ActiveRadarrBlock::Collections => { ActiveRadarrBlock::Collections => {
self
.dispatch_network_event(RadarrEvent::GetQualityProfiles.into())
.await;
self self
.dispatch_network_event(RadarrEvent::GetCollections.into()) .dispatch_network_event(RadarrEvent::GetCollections.into())
.await; .await;
self
.dispatch_network_event(RadarrEvent::GetMovies.into())
.await;
} }
ActiveRadarrBlock::CollectionDetails => { ActiveRadarrBlock::CollectionDetails => {
self
.dispatch_network_event(RadarrEvent::GetQualityProfiles.into())
.await;
self
.dispatch_network_event(RadarrEvent::GetTags.into())
.await;
self.is_loading = true; self.is_loading = true;
self.populate_movie_collection_table().await; self.populate_movie_collection_table().await;
self.is_loading = false; self.is_loading = false;
@@ -37,6 +49,12 @@ impl<'a> App<'a> {
.await; .await;
} }
ActiveRadarrBlock::Movies => { ActiveRadarrBlock::Movies => {
self
.dispatch_network_event(RadarrEvent::GetQualityProfiles.into())
.await;
self
.dispatch_network_event(RadarrEvent::GetTags.into())
.await;
self self
.dispatch_network_event(RadarrEvent::GetMovies.into()) .dispatch_network_event(RadarrEvent::GetMovies.into())
.await; .await;
@@ -45,6 +63,9 @@ impl<'a> App<'a> {
.await; .await;
} }
ActiveRadarrBlock::Indexers => { ActiveRadarrBlock::Indexers => {
self
.dispatch_network_event(RadarrEvent::GetTags.into())
.await;
self self
.dispatch_network_event(RadarrEvent::GetIndexers.into()) .dispatch_network_event(RadarrEvent::GetIndexers.into())
.await; .await;
@@ -56,7 +77,9 @@ impl<'a> App<'a> {
} }
ActiveRadarrBlock::TestIndexer => { ActiveRadarrBlock::TestIndexer => {
self self
.dispatch_network_event(RadarrEvent::TestIndexer(None).into()) .dispatch_network_event(
RadarrEvent::TestIndexer(self.extract_radarr_indexer_id().await).into(),
)
.await; .await;
} }
ActiveRadarrBlock::TestAllIndexers => { ActiveRadarrBlock::TestAllIndexers => {
@@ -72,7 +95,7 @@ impl<'a> App<'a> {
.dispatch_network_event(RadarrEvent::GetQueuedEvents.into()) .dispatch_network_event(RadarrEvent::GetQueuedEvents.into())
.await; .await;
self self
.dispatch_network_event(RadarrEvent::GetLogs(None).into()) .dispatch_network_event(RadarrEvent::GetLogs(500).into())
.await; .await;
} }
ActiveRadarrBlock::SystemUpdates => { ActiveRadarrBlock::SystemUpdates => {
@@ -82,17 +105,23 @@ impl<'a> App<'a> {
} }
ActiveRadarrBlock::AddMovieSearchResults => { ActiveRadarrBlock::AddMovieSearchResults => {
self self
.dispatch_network_event(RadarrEvent::SearchNewMovie(None).into()) .dispatch_network_event(
RadarrEvent::SearchNewMovie(self.extract_movie_search_query().await).into(),
)
.await; .await;
} }
ActiveRadarrBlock::MovieDetails | ActiveRadarrBlock::FileInfo => { ActiveRadarrBlock::MovieDetails | ActiveRadarrBlock::FileInfo => {
self self
.dispatch_network_event(RadarrEvent::GetMovieDetails(None).into()) .dispatch_network_event(
RadarrEvent::GetMovieDetails(self.extract_movie_id().await).into(),
)
.await; .await;
} }
ActiveRadarrBlock::MovieHistory => { ActiveRadarrBlock::MovieHistory => {
self self
.dispatch_network_event(RadarrEvent::GetMovieHistory(None).into()) .dispatch_network_event(
RadarrEvent::GetMovieHistory(self.extract_movie_id().await).into(),
)
.await; .await;
} }
ActiveRadarrBlock::Cast | ActiveRadarrBlock::Crew => { ActiveRadarrBlock::Cast | ActiveRadarrBlock::Crew => {
@@ -102,7 +131,9 @@ impl<'a> App<'a> {
|| movie_details_modal.movie_crew.items.is_empty() => || movie_details_modal.movie_crew.items.is_empty() =>
{ {
self self
.dispatch_network_event(RadarrEvent::GetMovieCredits(None).into()) .dispatch_network_event(
RadarrEvent::GetMovieCredits(self.extract_movie_id().await).into(),
)
.await; .await;
} }
_ => (), _ => (),
@@ -111,7 +142,7 @@ impl<'a> App<'a> {
ActiveRadarrBlock::ManualSearch => match self.data.radarr_data.movie_details_modal.as_ref() { ActiveRadarrBlock::ManualSearch => match self.data.radarr_data.movie_details_modal.as_ref() {
Some(movie_details_modal) if movie_details_modal.movie_releases.items.is_empty() => { Some(movie_details_modal) if movie_details_modal.movie_releases.items.is_empty() => {
self self
.dispatch_network_event(RadarrEvent::GetReleases(None).into()) .dispatch_network_event(RadarrEvent::GetReleases(self.extract_movie_id().await).into())
.await; .await;
} }
_ => (), _ => (),
@@ -119,11 +150,11 @@ impl<'a> App<'a> {
_ => (), _ => (),
} }
self.check_for_prompt_action().await; self.check_for_radarr_prompt_action().await;
self.reset_tick_count(); self.reset_tick_count();
} }
async fn check_for_prompt_action(&mut self) { async fn check_for_radarr_prompt_action(&mut self) {
if self.data.radarr_data.prompt_confirm { if self.data.radarr_data.prompt_confirm {
self.data.radarr_data.prompt_confirm = false; self.data.radarr_data.prompt_confirm = false;
if let Some(radarr_event) = &self.data.radarr_data.prompt_confirm_action { if let Some(radarr_event) = &self.data.radarr_data.prompt_confirm_action {
@@ -136,19 +167,17 @@ impl<'a> App<'a> {
} }
} }
pub(super) async fn radarr_on_tick( pub(super) async fn radarr_on_tick(&mut self, active_radarr_block: ActiveRadarrBlock) {
&mut self, if self.is_first_render {
active_radarr_block: ActiveRadarrBlock, self.refresh_radarr_metadata().await;
is_first_render: bool,
) {
if is_first_render {
self.refresh_metadata().await;
self.dispatch_by_radarr_block(&active_radarr_block).await; self.dispatch_by_radarr_block(&active_radarr_block).await;
self.is_first_render = false;
return;
} }
if self.should_refresh { if self.should_refresh {
self.dispatch_by_radarr_block(&active_radarr_block).await; self.dispatch_by_radarr_block(&active_radarr_block).await;
self.refresh_metadata().await; self.refresh_radarr_metadata().await;
} }
if self.is_routing { if self.is_routing {
@@ -156,16 +185,15 @@ impl<'a> App<'a> {
self.cancellation_token.cancel(); self.cancellation_token.cancel();
} else { } else {
self.dispatch_by_radarr_block(&active_radarr_block).await; self.dispatch_by_radarr_block(&active_radarr_block).await;
self.refresh_metadata().await;
} }
} }
if self.tick_count % self.tick_until_poll == 0 { if self.tick_count % self.tick_until_poll == 0 {
self.refresh_metadata().await; self.refresh_radarr_metadata().await;
} }
} }
async fn refresh_metadata(&mut self) { async fn refresh_radarr_metadata(&mut self) {
self self
.dispatch_network_event(RadarrEvent::GetQualityProfiles.into()) .dispatch_network_event(RadarrEvent::GetQualityProfiles.into())
.await; .await;
@@ -201,4 +229,29 @@ impl<'a> App<'a> {
.collection_movies .collection_movies
.set_items(collection_movies); .set_items(collection_movies);
} }
async fn extract_movie_id(&self) -> i64 {
self.data.radarr_data.movies.current_selection().clone().id
}
async fn extract_movie_search_query(&self) -> String {
self
.data
.radarr_data
.add_movie_search
.as_ref()
.expect("Add movie search is empty")
.text
.clone()
}
async fn extract_radarr_indexer_id(&self) -> i64 {
self
.data
.radarr_data
.indexers
.current_selection()
.clone()
.id
}
} }
+10 -62
View File
@@ -35,60 +35,6 @@ pub static COLLECTIONS_CONTEXT_CLUES: [ContextClue; 8] = [
(DEFAULT_KEYBINDINGS.esc, "cancel filter"), (DEFAULT_KEYBINDINGS.esc, "cancel filter"),
]; ];
pub static DOWNLOADS_CONTEXT_CLUES: [ContextClue; 2] = [
(
DEFAULT_KEYBINDINGS.refresh,
DEFAULT_KEYBINDINGS.refresh.desc,
),
(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc),
];
pub static BLOCKLIST_CONTEXT_CLUES: [ContextClue; 5] = [
(
DEFAULT_KEYBINDINGS.refresh,
DEFAULT_KEYBINDINGS.refresh.desc,
),
(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc),
(DEFAULT_KEYBINDINGS.submit, "details"),
(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc),
(DEFAULT_KEYBINDINGS.clear, "clear blocklist"),
];
pub static ROOT_FOLDERS_CONTEXT_CLUES: [ContextClue; 3] = [
(DEFAULT_KEYBINDINGS.add, DEFAULT_KEYBINDINGS.add.desc),
(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc),
(
DEFAULT_KEYBINDINGS.refresh,
DEFAULT_KEYBINDINGS.refresh.desc,
),
];
pub static INDEXERS_CONTEXT_CLUES: [ContextClue; 6] = [
(DEFAULT_KEYBINDINGS.submit, "edit indexer"),
(
DEFAULT_KEYBINDINGS.settings,
DEFAULT_KEYBINDINGS.settings.desc,
),
(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc),
(DEFAULT_KEYBINDINGS.test, "test indexer"),
(DEFAULT_KEYBINDINGS.test_all, "test all indexers"),
(
DEFAULT_KEYBINDINGS.refresh,
DEFAULT_KEYBINDINGS.refresh.desc,
),
];
pub static SYSTEM_CONTEXT_CLUES: [ContextClue; 5] = [
(DEFAULT_KEYBINDINGS.tasks, "open tasks"),
(DEFAULT_KEYBINDINGS.events, "open events"),
(DEFAULT_KEYBINDINGS.logs, "open logs"),
(DEFAULT_KEYBINDINGS.update, "open updates"),
(
DEFAULT_KEYBINDINGS.refresh,
DEFAULT_KEYBINDINGS.refresh.desc,
),
];
pub static MOVIE_DETAILS_CONTEXT_CLUES: [ContextClue; 5] = [ pub static MOVIE_DETAILS_CONTEXT_CLUES: [ContextClue; 5] = [
( (
DEFAULT_KEYBINDINGS.refresh, DEFAULT_KEYBINDINGS.refresh,
@@ -96,7 +42,10 @@ pub static MOVIE_DETAILS_CONTEXT_CLUES: [ContextClue; 5] = [
), ),
(DEFAULT_KEYBINDINGS.update, DEFAULT_KEYBINDINGS.update.desc), (DEFAULT_KEYBINDINGS.update, DEFAULT_KEYBINDINGS.update.desc),
(DEFAULT_KEYBINDINGS.edit, DEFAULT_KEYBINDINGS.edit.desc), (DEFAULT_KEYBINDINGS.edit, DEFAULT_KEYBINDINGS.edit.desc),
(DEFAULT_KEYBINDINGS.search, "auto search"), (
DEFAULT_KEYBINDINGS.auto_search,
DEFAULT_KEYBINDINGS.auto_search.desc,
),
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc), (DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
]; ];
@@ -108,7 +57,10 @@ pub static MANUAL_MOVIE_SEARCH_CONTEXT_CLUES: [ContextClue; 6] = [
(DEFAULT_KEYBINDINGS.update, DEFAULT_KEYBINDINGS.update.desc), (DEFAULT_KEYBINDINGS.update, DEFAULT_KEYBINDINGS.update.desc),
(DEFAULT_KEYBINDINGS.edit, DEFAULT_KEYBINDINGS.edit.desc), (DEFAULT_KEYBINDINGS.edit, DEFAULT_KEYBINDINGS.edit.desc),
(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc), (DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc),
(DEFAULT_KEYBINDINGS.search, "auto search"), (
DEFAULT_KEYBINDINGS.auto_search,
DEFAULT_KEYBINDINGS.auto_search.desc,
),
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc), (DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
]; ];
@@ -120,17 +72,13 @@ pub static ADD_MOVIE_SEARCH_RESULTS_CONTEXT_CLUES: [ContextClue; 2] = [
(DEFAULT_KEYBINDINGS.esc, "edit search"), (DEFAULT_KEYBINDINGS.esc, "edit search"),
]; ];
pub static CONFIRMATION_PROMPT_CONTEXT_CLUES: [ContextClue; 2] = [
(DEFAULT_KEYBINDINGS.confirm, "submit"),
(DEFAULT_KEYBINDINGS.esc, "cancel"),
];
pub static SYSTEM_TASKS_CONTEXT_CLUES: [ContextClue; 2] = [ pub static SYSTEM_TASKS_CONTEXT_CLUES: [ContextClue; 2] = [
(DEFAULT_KEYBINDINGS.submit, "start task"), (DEFAULT_KEYBINDINGS.submit, "start task"),
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc), (DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
]; ];
pub static COLLECTION_DETAILS_CONTEXT_CLUES: [ContextClue; 2] = [ pub static COLLECTION_DETAILS_CONTEXT_CLUES: [ContextClue; 3] = [
(DEFAULT_KEYBINDINGS.submit, "show overview/add movie"), (DEFAULT_KEYBINDINGS.submit, "show overview/add movie"),
(DEFAULT_KEYBINDINGS.edit, "edit collection"),
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc), (DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
]; ];
+13 -160
View File
@@ -4,11 +4,10 @@ mod tests {
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::radarr::radarr_context_clues::{ use crate::app::radarr::radarr_context_clues::{
ADD_MOVIE_SEARCH_RESULTS_CONTEXT_CLUES, BLOCKLIST_CONTEXT_CLUES, COLLECTIONS_CONTEXT_CLUES, ADD_MOVIE_SEARCH_RESULTS_CONTEXT_CLUES, COLLECTIONS_CONTEXT_CLUES,
COLLECTION_DETAILS_CONTEXT_CLUES, CONFIRMATION_PROMPT_CONTEXT_CLUES, DOWNLOADS_CONTEXT_CLUES, COLLECTION_DETAILS_CONTEXT_CLUES, LIBRARY_CONTEXT_CLUES,
INDEXERS_CONTEXT_CLUES, LIBRARY_CONTEXT_CLUES, MANUAL_MOVIE_SEARCH_CONTEXTUAL_CONTEXT_CLUES, MANUAL_MOVIE_SEARCH_CONTEXTUAL_CONTEXT_CLUES, MANUAL_MOVIE_SEARCH_CONTEXT_CLUES,
MANUAL_MOVIE_SEARCH_CONTEXT_CLUES, MOVIE_DETAILS_CONTEXT_CLUES, ROOT_FOLDERS_CONTEXT_CLUES, MOVIE_DETAILS_CONTEXT_CLUES, SYSTEM_TASKS_CONTEXT_CLUES,
SYSTEM_CONTEXT_CLUES, SYSTEM_TASKS_CONTEXT_CLUES,
}; };
#[test] #[test]
@@ -113,141 +112,6 @@ mod tests {
assert_eq!(collections_context_clues.next(), None); assert_eq!(collections_context_clues.next(), None);
} }
#[test]
fn test_downloads_context_clues() {
let mut downloads_context_clues_iter = DOWNLOADS_CONTEXT_CLUES.iter();
let (key_binding, description) = downloads_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
let (key_binding, description) = downloads_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.delete);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.delete.desc);
assert_eq!(downloads_context_clues_iter.next(), None);
}
#[test]
fn test_blocklist_context_clues() {
let mut blocklist_context_clues_iter = BLOCKLIST_CONTEXT_CLUES.iter();
let (key_binding, description) = blocklist_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
let (key_binding, description) = blocklist_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.sort);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.sort.desc);
let (key_binding, description) = blocklist_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit);
assert_str_eq!(*description, "details");
let (key_binding, description) = blocklist_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.delete);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.delete.desc);
let (key_binding, description) = blocklist_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.clear);
assert_str_eq!(*description, "clear blocklist");
assert_eq!(blocklist_context_clues_iter.next(), None);
}
#[test]
fn test_root_folders_context_clues() {
let mut root_folders_context_clues_iter = ROOT_FOLDERS_CONTEXT_CLUES.iter();
let (key_binding, description) = root_folders_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.add);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.add.desc);
let (key_binding, description) = root_folders_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.delete);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.delete.desc);
let (key_binding, description) = root_folders_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
assert_eq!(root_folders_context_clues_iter.next(), None);
}
#[test]
fn test_indexers_context_clues() {
let mut indexers_context_clues_iter = INDEXERS_CONTEXT_CLUES.iter();
let (key_binding, description) = indexers_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit);
assert_str_eq!(*description, "edit indexer");
let (key_binding, description) = indexers_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.settings);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.settings.desc);
let (key_binding, description) = indexers_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.delete);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.delete.desc);
let (key_binding, description) = indexers_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.test);
assert_str_eq!(*description, "test indexer");
let (key_binding, description) = indexers_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.test_all);
assert_str_eq!(*description, "test all indexers");
let (key_binding, description) = indexers_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
assert_eq!(indexers_context_clues_iter.next(), None);
}
#[test]
fn test_system_context_clues() {
let mut system_context_clues_iter = SYSTEM_CONTEXT_CLUES.iter();
let (key_binding, description) = system_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.tasks);
assert_str_eq!(*description, "open tasks");
let (key_binding, description) = system_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.events);
assert_str_eq!(*description, "open events");
let (key_binding, description) = system_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.logs);
assert_str_eq!(*description, "open logs");
let (key_binding, description) = system_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.update);
assert_str_eq!(*description, "open updates");
let (key_binding, description) = system_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
assert_eq!(system_context_clues_iter.next(), None);
}
#[test] #[test]
fn test_movie_details_context_clues() { fn test_movie_details_context_clues() {
let mut movie_details_context_clues_iter = MOVIE_DETAILS_CONTEXT_CLUES.iter(); let mut movie_details_context_clues_iter = MOVIE_DETAILS_CONTEXT_CLUES.iter();
@@ -269,8 +133,8 @@ mod tests {
let (key_binding, description) = movie_details_context_clues_iter.next().unwrap(); let (key_binding, description) = movie_details_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.search); assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.auto_search);
assert_str_eq!(*description, "auto search"); assert_str_eq!(*description, DEFAULT_KEYBINDINGS.auto_search.desc);
let (key_binding, description) = movie_details_context_clues_iter.next().unwrap(); let (key_binding, description) = movie_details_context_clues_iter.next().unwrap();
@@ -305,8 +169,8 @@ mod tests {
let (key_binding, description) = manual_movie_search_context_clues_iter.next().unwrap(); let (key_binding, description) = manual_movie_search_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.search); assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.auto_search);
assert_str_eq!(*description, "auto search"); assert_str_eq!(*description, DEFAULT_KEYBINDINGS.auto_search.desc);
let (key_binding, description) = manual_movie_search_context_clues_iter.next().unwrap(); let (key_binding, description) = manual_movie_search_context_clues_iter.next().unwrap();
@@ -349,22 +213,6 @@ mod tests {
assert_eq!(add_movie_search_results_context_clues_iter.next(), None); assert_eq!(add_movie_search_results_context_clues_iter.next(), None);
} }
#[test]
fn test_confirmation_prompt_context_clues() {
let mut confirmation_prompt_context_clues_iter = CONFIRMATION_PROMPT_CONTEXT_CLUES.iter();
let (key_binding, description) = confirmation_prompt_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.confirm);
assert_str_eq!(*description, "submit");
let (key_binding, description) = confirmation_prompt_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
assert_str_eq!(*description, "cancel");
assert_eq!(confirmation_prompt_context_clues_iter.next(), None);
}
#[test] #[test]
fn test_system_tasks_context_clues() { fn test_system_tasks_context_clues() {
let mut system_tasks_context_clues_iter = SYSTEM_TASKS_CONTEXT_CLUES.iter(); let mut system_tasks_context_clues_iter = SYSTEM_TASKS_CONTEXT_CLUES.iter();
@@ -392,6 +240,11 @@ mod tests {
let (key_binding, description) = collection_details_context_clues_iter.next().unwrap(); let (key_binding, description) = collection_details_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.edit);
assert_str_eq!(*description, "edit collection");
let (key_binding, description) = collection_details_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc); assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.esc.desc); assert_str_eq!(*description, DEFAULT_KEYBINDINGS.esc.desc);
assert_eq!(collection_details_context_clues_iter.next(), None); assert_eq!(collection_details_context_clues_iter.next(), None);
File diff suppressed because it is too large Load Diff
+309
View File
@@ -0,0 +1,309 @@
use crate::{
models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock,
network::sonarr_network::SonarrEvent,
};
use super::App;
pub mod sonarr_context_clues;
#[cfg(test)]
#[path = "sonarr_tests.rs"]
mod sonarr_tests;
impl<'a> App<'a> {
pub(super) async fn dispatch_by_sonarr_block(&mut self, active_sonarr_block: &ActiveSonarrBlock) {
match active_sonarr_block {
ActiveSonarrBlock::Series => {
self
.dispatch_network_event(SonarrEvent::GetQualityProfiles.into())
.await;
self
.dispatch_network_event(SonarrEvent::GetLanguageProfiles.into())
.await;
self
.dispatch_network_event(SonarrEvent::GetTags.into())
.await;
self
.dispatch_network_event(SonarrEvent::ListSeries.into())
.await;
}
ActiveSonarrBlock::SeriesDetails => {
self
.dispatch_network_event(SonarrEvent::ListSeries.into())
.await;
self.is_loading = true;
self.populate_seasons_table().await;
self.is_loading = false;
}
ActiveSonarrBlock::SeriesHistory => {
self
.dispatch_network_event(
SonarrEvent::GetSeriesHistory(self.extract_series_id().await).into(),
)
.await;
}
ActiveSonarrBlock::SeasonDetails => {
self
.dispatch_network_event(SonarrEvent::GetEpisodes(self.extract_series_id().await).into())
.await;
self
.dispatch_network_event(
SonarrEvent::GetEpisodeFiles(self.extract_series_id().await).into(),
)
.await;
self
.dispatch_network_event(SonarrEvent::GetDownloads.into())
.await;
}
ActiveSonarrBlock::SeasonHistory => {
if !self.data.sonarr_data.seasons.is_empty() {
self
.dispatch_network_event(
SonarrEvent::GetSeasonHistory(self.extract_series_id_season_number_tuple().await)
.into(),
)
.await;
}
}
ActiveSonarrBlock::ManualSeasonSearch => {
match self.data.sonarr_data.season_details_modal.as_ref() {
Some(season_details_modal) if season_details_modal.season_releases.is_empty() => {
self
.dispatch_network_event(
SonarrEvent::GetSeasonReleases(self.extract_series_id_season_number_tuple().await)
.into(),
)
.await;
}
_ => (),
}
}
ActiveSonarrBlock::EpisodeDetails | ActiveSonarrBlock::EpisodeFile => {
self
.dispatch_network_event(
SonarrEvent::GetEpisodeDetails(self.extract_episode_id().await).into(),
)
.await;
}
ActiveSonarrBlock::EpisodeHistory => {
self
.dispatch_network_event(
SonarrEvent::GetEpisodeHistory(self.extract_episode_id().await).into(),
)
.await;
}
ActiveSonarrBlock::ManualEpisodeSearch => {
if let Some(season_details_modal) = self.data.sonarr_data.season_details_modal.as_ref() {
if let Some(episode_details_modal) = season_details_modal.episode_details_modal.as_ref() {
if episode_details_modal.episode_releases.is_empty() {
self
.dispatch_network_event(
SonarrEvent::GetEpisodeReleases(self.extract_episode_id().await).into(),
)
.await;
}
}
}
}
ActiveSonarrBlock::Downloads => {
self
.dispatch_network_event(SonarrEvent::GetDownloads.into())
.await;
}
ActiveSonarrBlock::Blocklist => {
self
.dispatch_network_event(SonarrEvent::ListSeries.into())
.await;
self
.dispatch_network_event(SonarrEvent::GetBlocklist.into())
.await;
}
ActiveSonarrBlock::History => {
self
.dispatch_network_event(SonarrEvent::GetHistory(500).into())
.await;
}
ActiveSonarrBlock::RootFolders => {
self
.dispatch_network_event(SonarrEvent::GetRootFolders.into())
.await;
}
ActiveSonarrBlock::Indexers => {
self
.dispatch_network_event(SonarrEvent::GetTags.into())
.await;
self
.dispatch_network_event(SonarrEvent::GetIndexers.into())
.await;
}
ActiveSonarrBlock::AllIndexerSettingsPrompt => {
self
.dispatch_network_event(SonarrEvent::GetAllIndexerSettings.into())
.await;
}
ActiveSonarrBlock::TestIndexer => {
self
.dispatch_network_event(
SonarrEvent::TestIndexer(self.extract_sonarr_indexer_id().await).into(),
)
.await;
}
ActiveSonarrBlock::TestAllIndexers => {
self
.dispatch_network_event(SonarrEvent::TestAllIndexers.into())
.await;
}
ActiveSonarrBlock::System => {
self
.dispatch_network_event(SonarrEvent::GetTasks.into())
.await;
self
.dispatch_network_event(SonarrEvent::GetQueuedEvents.into())
.await;
self
.dispatch_network_event(SonarrEvent::GetLogs(500).into())
.await;
}
ActiveSonarrBlock::AddSeriesSearchResults => {
self
.dispatch_network_event(
SonarrEvent::SearchNewSeries(self.extract_add_new_series_search_query().await).into(),
)
.await;
}
ActiveSonarrBlock::SystemUpdates => {
self
.dispatch_network_event(SonarrEvent::GetUpdates.into())
.await;
}
_ => (),
}
self.check_for_sonarr_prompt_action().await;
self.reset_tick_count();
}
async fn check_for_sonarr_prompt_action(&mut self) {
if self.data.sonarr_data.prompt_confirm {
self.data.sonarr_data.prompt_confirm = false;
if let Some(sonarr_event) = &self.data.sonarr_data.prompt_confirm_action {
self
.dispatch_network_event(sonarr_event.clone().into())
.await;
self.should_refresh = true;
self.data.sonarr_data.prompt_confirm_action = None;
}
}
}
pub(super) async fn sonarr_on_tick(&mut self, active_sonarr_block: ActiveSonarrBlock) {
if self.is_first_render {
self.refresh_sonarr_metadata().await;
self.dispatch_by_sonarr_block(&active_sonarr_block).await;
self.is_first_render = false;
return;
}
if self.should_refresh {
self.dispatch_by_sonarr_block(&active_sonarr_block).await;
self.refresh_sonarr_metadata().await;
}
if self.is_routing {
if !self.should_refresh {
self.cancellation_token.cancel();
} else {
self.dispatch_by_sonarr_block(&active_sonarr_block).await;
}
}
if self.tick_count % self.tick_until_poll == 0 {
self.refresh_sonarr_metadata().await;
}
}
async fn refresh_sonarr_metadata(&mut self) {
self
.dispatch_network_event(SonarrEvent::GetQualityProfiles.into())
.await;
self
.dispatch_network_event(SonarrEvent::GetLanguageProfiles.into())
.await;
self
.dispatch_network_event(SonarrEvent::GetTags.into())
.await;
self
.dispatch_network_event(SonarrEvent::GetRootFolders.into())
.await;
self
.dispatch_network_event(SonarrEvent::GetDownloads.into())
.await;
self
.dispatch_network_event(SonarrEvent::GetDiskSpace.into())
.await;
self
.dispatch_network_event(SonarrEvent::GetStatus.into())
.await;
}
async fn populate_seasons_table(&mut self) {
let seasons = self
.data
.sonarr_data
.series
.current_selection()
.clone()
.seasons
.unwrap_or_default()
.into_iter()
.map(|mut season| {
season.title = Some(format!("Season {}", season.season_number));
season
})
.collect();
self.data.sonarr_data.seasons.set_items(seasons);
}
async fn extract_episode_id(&self) -> i64 {
self
.data
.sonarr_data
.season_details_modal
.as_ref()
.expect("Season details have not been loaded")
.episodes
.current_selection()
.id
}
async fn extract_series_id(&self) -> i64 {
self.data.sonarr_data.series.current_selection().id
}
async fn extract_series_id_season_number_tuple(&self) -> (i64, i64) {
let series_id = self.data.sonarr_data.series.current_selection().id;
let season_number = self
.data
.sonarr_data
.seasons
.current_selection()
.season_number;
(series_id, season_number)
}
async fn extract_add_new_series_search_query(&self) -> String {
self
.data
.sonarr_data
.add_series_search
.as_ref()
.expect("Add series search is empty")
.text
.clone()
}
async fn extract_sonarr_indexer_id(&self) -> i64 {
self.data.sonarr_data.indexers.current_selection().id
}
}
+159
View File
@@ -0,0 +1,159 @@
use crate::app::{context_clues::ContextClue, key_binding::DEFAULT_KEYBINDINGS};
#[cfg(test)]
#[path = "sonarr_context_clues_tests.rs"]
mod sonarr_context_clues_tests;
pub static ADD_SERIES_SEARCH_RESULTS_CONTEXT_CLUES: [ContextClue; 2] = [
(DEFAULT_KEYBINDINGS.submit, "details"),
(DEFAULT_KEYBINDINGS.esc, "edit search"),
];
pub static SERIES_CONTEXT_CLUES: [ContextClue; 10] = [
(DEFAULT_KEYBINDINGS.add, DEFAULT_KEYBINDINGS.add.desc),
(DEFAULT_KEYBINDINGS.edit, DEFAULT_KEYBINDINGS.edit.desc),
(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc),
(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc),
(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc),
(DEFAULT_KEYBINDINGS.filter, DEFAULT_KEYBINDINGS.filter.desc),
(
DEFAULT_KEYBINDINGS.refresh,
DEFAULT_KEYBINDINGS.refresh.desc,
),
(DEFAULT_KEYBINDINGS.update, "update all"),
(DEFAULT_KEYBINDINGS.submit, "details"),
(DEFAULT_KEYBINDINGS.esc, "cancel filter"),
];
pub static SERIES_DETAILS_CONTEXT_CLUES: [ContextClue; 8] = [
(
DEFAULT_KEYBINDINGS.refresh,
DEFAULT_KEYBINDINGS.refresh.desc,
),
(DEFAULT_KEYBINDINGS.edit, DEFAULT_KEYBINDINGS.edit.desc),
(
DEFAULT_KEYBINDINGS.toggle_monitoring,
DEFAULT_KEYBINDINGS.toggle_monitoring.desc,
),
(DEFAULT_KEYBINDINGS.submit, "season details"),
(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc),
(DEFAULT_KEYBINDINGS.update, DEFAULT_KEYBINDINGS.update.desc),
(
DEFAULT_KEYBINDINGS.auto_search,
DEFAULT_KEYBINDINGS.auto_search.desc,
),
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
];
pub static HISTORY_CONTEXT_CLUES: [ContextClue; 6] = [
(DEFAULT_KEYBINDINGS.submit, "details"),
(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc),
(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc),
(DEFAULT_KEYBINDINGS.filter, DEFAULT_KEYBINDINGS.filter.desc),
(
DEFAULT_KEYBINDINGS.refresh,
DEFAULT_KEYBINDINGS.refresh.desc,
),
(DEFAULT_KEYBINDINGS.esc, "cancel filter"),
];
pub static SERIES_HISTORY_CONTEXT_CLUES: [ContextClue; 9] = [
(
DEFAULT_KEYBINDINGS.refresh,
DEFAULT_KEYBINDINGS.refresh.desc,
),
(DEFAULT_KEYBINDINGS.edit, DEFAULT_KEYBINDINGS.edit.desc),
(DEFAULT_KEYBINDINGS.submit, "details"),
(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc),
(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc),
(DEFAULT_KEYBINDINGS.filter, DEFAULT_KEYBINDINGS.filter.desc),
(
DEFAULT_KEYBINDINGS.auto_search,
DEFAULT_KEYBINDINGS.auto_search.desc,
),
(DEFAULT_KEYBINDINGS.update, DEFAULT_KEYBINDINGS.update.desc),
(DEFAULT_KEYBINDINGS.esc, "cancel filter/close"),
];
pub static SEASON_DETAILS_CONTEXTUAL_CONTEXT_CLUES: [ContextClue; 2] = [
(DEFAULT_KEYBINDINGS.submit, "episode details"),
(DEFAULT_KEYBINDINGS.delete, "delete episode"),
];
pub static SEASON_DETAILS_CONTEXT_CLUES: [ContextClue; 5] = [
(
DEFAULT_KEYBINDINGS.refresh,
DEFAULT_KEYBINDINGS.refresh.desc,
),
(
DEFAULT_KEYBINDINGS.toggle_monitoring,
DEFAULT_KEYBINDINGS.toggle_monitoring.desc,
),
(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc),
(
DEFAULT_KEYBINDINGS.auto_search,
DEFAULT_KEYBINDINGS.auto_search.desc,
),
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
];
pub static SEASON_HISTORY_CONTEXT_CLUES: [ContextClue; 6] = [
(
DEFAULT_KEYBINDINGS.refresh,
DEFAULT_KEYBINDINGS.refresh.desc,
),
(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc),
(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc),
(DEFAULT_KEYBINDINGS.filter, DEFAULT_KEYBINDINGS.filter.desc),
(
DEFAULT_KEYBINDINGS.auto_search,
DEFAULT_KEYBINDINGS.auto_search.desc,
),
(DEFAULT_KEYBINDINGS.esc, "cancel filter/close"),
];
pub static MANUAL_SEASON_SEARCH_CONTEXT_CLUES: [ContextClue; 4] = [
(
DEFAULT_KEYBINDINGS.refresh,
DEFAULT_KEYBINDINGS.refresh.desc,
),
(
DEFAULT_KEYBINDINGS.auto_search,
DEFAULT_KEYBINDINGS.auto_search.desc,
),
(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc),
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
];
pub static MANUAL_EPISODE_SEARCH_CONTEXT_CLUES: [ContextClue; 4] = [
(
DEFAULT_KEYBINDINGS.refresh,
DEFAULT_KEYBINDINGS.refresh.desc,
),
(
DEFAULT_KEYBINDINGS.auto_search,
DEFAULT_KEYBINDINGS.auto_search.desc,
),
(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc),
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
];
pub static DETAILS_CONTEXTUAL_CONTEXT_CLUES: [ContextClue; 1] =
[(DEFAULT_KEYBINDINGS.submit, "details")];
pub static EPISODE_DETAILS_CONTEXT_CLUES: [ContextClue; 3] = [
(
DEFAULT_KEYBINDINGS.refresh,
DEFAULT_KEYBINDINGS.refresh.desc,
),
(
DEFAULT_KEYBINDINGS.auto_search,
DEFAULT_KEYBINDINGS.auto_search.desc,
),
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
];
pub static SYSTEM_TASKS_CONTEXT_CLUES: [ContextClue; 2] = [
(DEFAULT_KEYBINDINGS.submit, "start task"),
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
];
@@ -0,0 +1,402 @@
#[cfg(test)]
mod tests {
use pretty_assertions::{assert_eq, assert_str_eq};
use crate::app::{
key_binding::DEFAULT_KEYBINDINGS,
sonarr::sonarr_context_clues::{
ADD_SERIES_SEARCH_RESULTS_CONTEXT_CLUES, DETAILS_CONTEXTUAL_CONTEXT_CLUES,
EPISODE_DETAILS_CONTEXT_CLUES, HISTORY_CONTEXT_CLUES, MANUAL_EPISODE_SEARCH_CONTEXT_CLUES,
MANUAL_SEASON_SEARCH_CONTEXT_CLUES, SEASON_DETAILS_CONTEXTUAL_CONTEXT_CLUES,
SEASON_DETAILS_CONTEXT_CLUES, SEASON_HISTORY_CONTEXT_CLUES, SERIES_CONTEXT_CLUES,
SERIES_DETAILS_CONTEXT_CLUES, SERIES_HISTORY_CONTEXT_CLUES, SYSTEM_TASKS_CONTEXT_CLUES,
},
};
#[test]
fn test_add_series_search_results_context_clues() {
let mut add_series_search_results_context_clues_iter =
ADD_SERIES_SEARCH_RESULTS_CONTEXT_CLUES.iter();
let (key_binding, description) = add_series_search_results_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit);
assert_str_eq!(*description, "details");
let (key_binding, description) = add_series_search_results_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
assert_str_eq!(*description, "edit search");
assert_eq!(add_series_search_results_context_clues_iter.next(), None);
}
#[test]
fn test_series_context_clues() {
let mut series_context_clues_iter = SERIES_CONTEXT_CLUES.iter();
let (key_binding, description) = series_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.add);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.add.desc);
let (key_binding, description) = series_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.edit);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.edit.desc);
let (key_binding, description) = series_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.sort);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.sort.desc);
let (key_binding, description) = series_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.delete);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.delete.desc);
let (key_binding, description) = series_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.search);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.search.desc);
let (key_binding, description) = series_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.filter);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.filter.desc);
let (key_binding, description) = series_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
let (key_binding, description) = series_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.update);
assert_str_eq!(*description, "update all");
let (key_binding, description) = series_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit);
assert_str_eq!(*description, "details");
let (key_binding, description) = series_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
assert_str_eq!(*description, "cancel filter");
assert_eq!(series_context_clues_iter.next(), None);
}
#[test]
fn test_series_history_context_clues() {
let mut series_history_context_clues_iter = SERIES_HISTORY_CONTEXT_CLUES.iter();
let (key_binding, description) = series_history_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
let (key_binding, description) = series_history_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.edit);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.edit.desc);
let (key_binding, description) = series_history_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit);
assert_str_eq!(*description, "details");
let (key_binding, description) = series_history_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.sort);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.sort.desc);
let (key_binding, description) = series_history_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.search);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.search.desc);
let (key_binding, description) = series_history_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.filter);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.filter.desc);
let (key_binding, description) = series_history_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.auto_search);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.auto_search.desc);
let (key_binding, description) = series_history_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.update);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.update.desc);
let (key_binding, description) = series_history_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
assert_str_eq!(*description, "cancel filter/close");
assert_eq!(series_history_context_clues_iter.next(), None);
}
#[test]
fn test_history_context_clues() {
let mut history_context_clues_iter = HISTORY_CONTEXT_CLUES.iter();
let (key_binding, description) = history_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit);
assert_str_eq!(*description, "details");
let (key_binding, description) = history_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.sort);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.sort.desc);
let (key_binding, description) = history_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.search);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.search.desc);
let (key_binding, description) = history_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.filter);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.filter.desc);
let (key_binding, description) = history_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
let (key_binding, description) = history_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
assert_str_eq!(*description, "cancel filter");
assert_eq!(history_context_clues_iter.next(), None);
}
#[test]
fn test_series_details_context_clues() {
let mut series_details_context_clues_iter = SERIES_DETAILS_CONTEXT_CLUES.iter();
let (key_binding, description) = series_details_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
let (key_binding, description) = series_details_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.edit);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.edit.desc);
let (key_binding, description) = series_details_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.toggle_monitoring);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.toggle_monitoring.desc);
let (key_binding, description) = series_details_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit);
assert_str_eq!(*description, "season details");
let (key_binding, description) = series_details_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.search);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.search.desc);
let (key_binding, description) = series_details_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.update);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.update.desc);
let (key_binding, description) = series_details_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.auto_search);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.auto_search.desc);
let (key_binding, description) = series_details_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.esc.desc);
assert_eq!(series_details_context_clues_iter.next(), None);
}
#[test]
fn test_season_details_context_clues() {
let mut season_details_context_clues_iter = SEASON_DETAILS_CONTEXT_CLUES.iter();
let (key_binding, description) = season_details_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
let (key_binding, description) = season_details_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.toggle_monitoring);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.toggle_monitoring.desc);
let (key_binding, description) = season_details_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.search);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.search.desc);
let (key_binding, description) = season_details_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.auto_search);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.auto_search.desc);
let (key_binding, description) = season_details_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.esc.desc);
assert_eq!(season_details_context_clues_iter.next(), None);
}
#[test]
fn test_season_details_contextual_context_clues() {
let mut season_details_contextual_context_clues_iter =
SEASON_DETAILS_CONTEXTUAL_CONTEXT_CLUES.iter();
let (key_binding, description) = season_details_contextual_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit);
assert_str_eq!(*description, "episode details");
let (key_binding, description) = season_details_contextual_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.delete);
assert_str_eq!(*description, "delete episode");
assert_eq!(season_details_contextual_context_clues_iter.next(), None);
}
#[test]
fn test_season_history_context_clues() {
let mut season_history_context_clues_iter = SEASON_HISTORY_CONTEXT_CLUES.iter();
let (key_binding, description) = season_history_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
let (key_binding, description) = season_history_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.sort);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.sort.desc);
let (key_binding, description) = season_history_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.search);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.search.desc);
let (key_binding, description) = season_history_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.filter);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.filter.desc);
let (key_binding, description) = season_history_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.auto_search);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.auto_search.desc);
let (key_binding, description) = season_history_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
assert_str_eq!(*description, "cancel filter/close");
assert_eq!(season_history_context_clues_iter.next(), None);
}
#[test]
fn test_manual_season_search_context_clues() {
let mut manual_season_search_context_clues_iter = MANUAL_SEASON_SEARCH_CONTEXT_CLUES.iter();
let (key_binding, description) = manual_season_search_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
let (key_binding, description) = manual_season_search_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.auto_search);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.auto_search.desc);
let (key_binding, description) = manual_season_search_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.sort);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.sort.desc);
let (key_binding, description) = manual_season_search_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.esc.desc);
assert_eq!(manual_season_search_context_clues_iter.next(), None);
}
#[test]
fn test_manual_episode_search_context_clues() {
let mut manual_episode_search_context_clues_iter = MANUAL_EPISODE_SEARCH_CONTEXT_CLUES.iter();
let (key_binding, description) = manual_episode_search_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
let (key_binding, description) = manual_episode_search_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.auto_search);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.auto_search.desc);
let (key_binding, description) = manual_episode_search_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.sort);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.sort.desc);
let (key_binding, description) = manual_episode_search_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.esc.desc);
assert_eq!(manual_episode_search_context_clues_iter.next(), None);
}
#[test]
fn details_contextual_context_clues() {
let mut manual_search_contextual_context_clues_iter = DETAILS_CONTEXTUAL_CONTEXT_CLUES.iter();
let (key_binding, description) = manual_search_contextual_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit);
assert_str_eq!(*description, "details");
assert_eq!(manual_search_contextual_context_clues_iter.next(), None);
}
#[test]
fn test_episode_details_context_clues() {
let mut episode_details_context_clues_iter = EPISODE_DETAILS_CONTEXT_CLUES.iter();
let (key_binding, description) = episode_details_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
let (key_binding, description) = episode_details_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.auto_search);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.auto_search.desc);
let (key_binding, description) = episode_details_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.esc.desc);
assert_eq!(episode_details_context_clues_iter.next(), None);
}
#[test]
fn test_system_tasks_context_clues() {
let mut system_tasks_context_clues_iter = SYSTEM_TASKS_CONTEXT_CLUES.iter();
let (key_binding, description) = system_tasks_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit);
assert_str_eq!(*description, "start task");
let (key_binding, description) = system_tasks_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.esc.desc);
assert_eq!(system_tasks_context_clues_iter.next(), None);
}
}
+881
View File
@@ -0,0 +1,881 @@
#[cfg(test)]
mod tests {
mod sonarr_tests {
use pretty_assertions::{assert_eq, assert_str_eq};
use tokio::sync::mpsc;
use crate::models::servarr_data::sonarr::sonarr_data::sonarr_test_utils::utils::create_test_sonarr_data;
use crate::models::servarr_models::Indexer;
use crate::models::sonarr_models::Episode;
use crate::{
app::App,
models::{
servarr_data::sonarr::{
modals::{EpisodeDetailsModal, SeasonDetailsModal},
sonarr_data::ActiveSonarrBlock,
},
sonarr_models::{Season, Series, SonarrRelease},
},
network::{sonarr_network::SonarrEvent, NetworkEvent},
};
#[tokio::test]
async fn test_dispatch_by_blocklist_block() {
let (mut app, mut sync_network_rx) = construct_app_unit();
app
.dispatch_by_sonarr_block(&ActiveSonarrBlock::Blocklist)
.await;
assert!(app.is_loading);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::ListSeries.into()
);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetBlocklist.into()
);
assert!(!app.data.sonarr_data.prompt_confirm);
assert_eq!(app.tick_count, 0);
}
#[tokio::test]
async fn test_dispatch_by_series_history_block() {
let (mut app, mut sync_network_rx) = construct_app_unit();
app.data.sonarr_data.series.set_items(vec![Series {
id: 1,
..Series::default()
}]);
app
.dispatch_by_sonarr_block(&ActiveSonarrBlock::SeriesHistory)
.await;
assert!(app.is_loading);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetSeriesHistory(1).into()
);
assert!(!app.data.sonarr_data.prompt_confirm);
assert_eq!(app.tick_count, 0);
}
#[tokio::test]
async fn test_dispatch_by_series_details_block() {
let (mut app, mut sync_network_rx) = construct_app_unit();
app.data.sonarr_data.series.set_items(vec![Series {
seasons: Some(vec![Season::default()]),
..Series::default()
}]);
app
.dispatch_by_sonarr_block(&ActiveSonarrBlock::SeriesDetails)
.await;
assert!(!app.is_loading);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::ListSeries.into()
);
assert!(!app.data.sonarr_data.seasons.items.is_empty());
assert_eq!(app.tick_count, 0);
assert!(!app.data.sonarr_data.prompt_confirm);
}
#[tokio::test]
async fn test_dispatch_by_season_details_block() {
let (mut app, mut sync_network_rx) = construct_app_unit();
app.data.sonarr_data.series.set_items(vec![Series {
id: 1,
..Series::default()
}]);
app
.dispatch_by_sonarr_block(&ActiveSonarrBlock::SeasonDetails)
.await;
assert!(app.is_loading);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetEpisodes(1).into()
);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetEpisodeFiles(1).into()
);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetDownloads.into()
);
assert!(!app.data.sonarr_data.prompt_confirm);
assert_eq!(app.tick_count, 0);
}
#[tokio::test]
async fn test_dispatch_by_season_history_block() {
let (mut app, mut sync_network_rx) = construct_app_unit();
app.data.sonarr_data.series.set_items(vec![Series {
id: 1,
..Series::default()
}]);
app.data.sonarr_data.seasons.set_items(vec![Season {
season_number: 1,
..Season::default()
}]);
app
.dispatch_by_sonarr_block(&ActiveSonarrBlock::SeasonHistory)
.await;
assert!(app.is_loading);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetSeasonHistory((1, 1)).into()
);
assert!(!app.data.sonarr_data.prompt_confirm);
assert_eq!(app.tick_count, 0);
}
#[tokio::test]
async fn test_dispatch_by_season_history_block_no_op_when_seasons_table_is_empty() {
let (mut app, _) = construct_app_unit();
app.data.sonarr_data.series.set_items(vec![Series {
id: 1,
..Series::default()
}]);
app
.dispatch_by_sonarr_block(&ActiveSonarrBlock::SeasonHistory)
.await;
assert!(!app.is_loading);
assert!(!app.data.sonarr_data.prompt_confirm);
assert_eq!(app.tick_count, 0);
}
#[tokio::test]
async fn test_dispatch_by_manual_season_search_block() {
let (mut app, mut sync_network_rx) = construct_app_unit();
app.data.sonarr_data.season_details_modal = Some(SeasonDetailsModal::default());
app.data.sonarr_data.series.set_items(vec![Series {
id: 1,
..Series::default()
}]);
app.data.sonarr_data.seasons.set_items(vec![Season {
season_number: 1,
..Season::default()
}]);
app
.dispatch_by_sonarr_block(&ActiveSonarrBlock::ManualSeasonSearch)
.await;
assert!(app.is_loading);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetSeasonReleases((1, 1)).into()
);
assert!(!app.data.sonarr_data.prompt_confirm);
assert_eq!(app.tick_count, 0);
}
#[tokio::test]
async fn test_dispatch_by_manual_season_search_block_is_loading() {
let mut app = App {
is_loading: true,
..App::default()
};
app
.dispatch_by_sonarr_block(&ActiveSonarrBlock::ManualSeasonSearch)
.await;
assert!(app.is_loading);
assert!(!app.data.sonarr_data.prompt_confirm);
assert_eq!(app.tick_count, 0);
}
#[tokio::test]
async fn test_dispatch_by_manual_season_search_block_season_releases_non_empty() {
let mut app = App::default();
let mut season_details_modal = SeasonDetailsModal::default();
season_details_modal
.season_releases
.set_items(vec![SonarrRelease::default()]);
app.data.sonarr_data.season_details_modal = Some(season_details_modal);
app
.dispatch_by_sonarr_block(&ActiveSonarrBlock::ManualSeasonSearch)
.await;
assert!(!app.is_loading);
assert!(!app.data.sonarr_data.prompt_confirm);
assert_eq!(app.tick_count, 0);
}
#[tokio::test]
async fn test_dispatch_by_episode_details_block() {
let (mut app, mut sync_network_rx) = construct_app_unit();
app.data.sonarr_data = create_test_sonarr_data();
app
.dispatch_by_sonarr_block(&ActiveSonarrBlock::EpisodeDetails)
.await;
assert!(app.is_loading);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetEpisodeDetails(0).into()
);
assert!(!app.data.sonarr_data.prompt_confirm);
assert_eq!(app.tick_count, 0);
}
#[tokio::test]
async fn test_dispatch_by_episode_file_block() {
let (mut app, mut sync_network_rx) = construct_app_unit();
app.data.sonarr_data = create_test_sonarr_data();
app
.dispatch_by_sonarr_block(&ActiveSonarrBlock::EpisodeFile)
.await;
assert!(app.is_loading);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetEpisodeDetails(0).into()
);
assert!(!app.data.sonarr_data.prompt_confirm);
assert_eq!(app.tick_count, 0);
}
#[tokio::test]
async fn test_dispatch_by_episode_history_block() {
let (mut app, mut sync_network_rx) = construct_app_unit();
let mut season_details_modal = SeasonDetailsModal::default();
season_details_modal.episodes.set_items(vec![Episode {
id: 1,
..Episode::default()
}]);
app.data.sonarr_data.season_details_modal = Some(season_details_modal);
app
.dispatch_by_sonarr_block(&ActiveSonarrBlock::EpisodeHistory)
.await;
assert!(app.is_loading);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetEpisodeHistory(1).into()
);
assert!(!app.data.sonarr_data.prompt_confirm);
assert_eq!(app.tick_count, 0);
}
#[tokio::test]
async fn test_dispatch_by_manual_episode_search_block() {
let (mut app, mut sync_network_rx) = construct_app_unit();
let mut season_details_modal = SeasonDetailsModal {
episode_details_modal: Some(EpisodeDetailsModal::default()),
..SeasonDetailsModal::default()
};
season_details_modal.episodes.set_items(vec![Episode {
id: 1,
..Episode::default()
}]);
app.data.sonarr_data.season_details_modal = Some(season_details_modal);
app
.dispatch_by_sonarr_block(&ActiveSonarrBlock::ManualEpisodeSearch)
.await;
assert!(app.is_loading);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetEpisodeReleases(1).into()
);
assert!(!app.data.sonarr_data.prompt_confirm);
assert_eq!(app.tick_count, 0);
}
#[tokio::test]
async fn test_dispatch_by_manual_episode_search_block_is_loading() {
let mut app = App {
is_loading: true,
..App::default()
};
app
.dispatch_by_sonarr_block(&ActiveSonarrBlock::ManualEpisodeSearch)
.await;
assert!(app.is_loading);
assert!(!app.data.sonarr_data.prompt_confirm);
assert_eq!(app.tick_count, 0);
}
#[tokio::test]
async fn test_dispatch_by_manual_episode_search_block_episode_releases_non_empty() {
let mut app = App::default();
let mut episode_details_modal = EpisodeDetailsModal::default();
episode_details_modal
.episode_releases
.set_items(vec![SonarrRelease::default()]);
let season_details_modal = SeasonDetailsModal {
episode_details_modal: Some(episode_details_modal),
..SeasonDetailsModal::default()
};
app.data.sonarr_data.season_details_modal = Some(season_details_modal);
app
.dispatch_by_sonarr_block(&ActiveSonarrBlock::ManualEpisodeSearch)
.await;
assert!(!app.is_loading);
assert!(!app.data.sonarr_data.prompt_confirm);
assert_eq!(app.tick_count, 0);
}
#[tokio::test]
async fn test_dispatch_by_history_block() {
let (mut app, mut sync_network_rx) = construct_app_unit();
app
.dispatch_by_sonarr_block(&ActiveSonarrBlock::History)
.await;
assert!(app.is_loading);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetHistory(500).into()
);
assert!(!app.data.sonarr_data.prompt_confirm);
assert_eq!(app.tick_count, 0);
}
#[tokio::test]
async fn test_dispatch_by_downloads_block() {
let (mut app, mut sync_network_rx) = construct_app_unit();
app
.dispatch_by_sonarr_block(&ActiveSonarrBlock::Downloads)
.await;
assert!(app.is_loading);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetDownloads.into()
);
assert!(!app.data.sonarr_data.prompt_confirm);
assert_eq!(app.tick_count, 0);
}
#[tokio::test]
async fn test_dispatch_by_root_folders_block() {
let (mut app, mut sync_network_rx) = construct_app_unit();
app
.dispatch_by_sonarr_block(&ActiveSonarrBlock::RootFolders)
.await;
assert!(app.is_loading);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetRootFolders.into()
);
assert!(!app.data.sonarr_data.prompt_confirm);
assert_eq!(app.tick_count, 0);
}
#[tokio::test]
async fn test_dispatch_by_series_block() {
let (mut app, mut sync_network_rx) = construct_app_unit();
app
.dispatch_by_sonarr_block(&ActiveSonarrBlock::Series)
.await;
assert!(app.is_loading);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetQualityProfiles.into()
);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetLanguageProfiles.into()
);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetTags.into()
);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::ListSeries.into()
);
assert!(!app.data.sonarr_data.prompt_confirm);
assert_eq!(app.tick_count, 0);
}
#[tokio::test]
async fn test_dispatch_by_indexers_block() {
let (mut app, mut sync_network_rx) = construct_app_unit();
app
.dispatch_by_sonarr_block(&ActiveSonarrBlock::Indexers)
.await;
assert!(app.is_loading);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetTags.into()
);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetIndexers.into()
);
assert!(!app.data.sonarr_data.prompt_confirm);
assert_eq!(app.tick_count, 0);
}
#[tokio::test]
async fn test_dispatch_by_all_indexer_settings_block() {
let (mut app, mut sync_network_rx) = construct_app_unit();
app
.dispatch_by_sonarr_block(&ActiveSonarrBlock::AllIndexerSettingsPrompt)
.await;
assert!(app.is_loading);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetAllIndexerSettings.into()
);
assert!(!app.data.sonarr_data.prompt_confirm);
assert_eq!(app.tick_count, 0);
}
#[tokio::test]
async fn test_dispatch_by_test_indexer_block() {
let (mut app, mut sync_network_rx) = construct_app_unit();
app.data.sonarr_data.indexers.set_items(vec![Indexer {
id: 1,
..Indexer::default()
}]);
app
.dispatch_by_sonarr_block(&ActiveSonarrBlock::TestIndexer)
.await;
assert!(app.is_loading);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::TestIndexer(1).into()
);
assert_eq!(app.tick_count, 0);
}
#[tokio::test]
async fn test_dispatch_by_test_all_indexers_block() {
let (mut app, mut sync_network_rx) = construct_app_unit();
app
.dispatch_by_sonarr_block(&ActiveSonarrBlock::TestAllIndexers)
.await;
assert!(app.is_loading);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::TestAllIndexers.into()
);
assert_eq!(app.tick_count, 0);
}
#[tokio::test]
async fn test_dispatch_by_system_block() {
let (mut app, mut sync_network_rx) = construct_app_unit();
app
.dispatch_by_sonarr_block(&ActiveSonarrBlock::System)
.await;
assert!(app.is_loading);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetTasks.into()
);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetQueuedEvents.into()
);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetLogs(500).into()
);
assert!(!app.data.sonarr_data.prompt_confirm);
assert_eq!(app.tick_count, 0);
}
#[tokio::test]
async fn test_dispatch_by_system_updates_block() {
let (mut app, mut sync_network_rx) = construct_app_unit();
app
.dispatch_by_sonarr_block(&ActiveSonarrBlock::SystemUpdates)
.await;
assert!(app.is_loading);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetUpdates.into()
);
assert!(!app.data.sonarr_data.prompt_confirm);
assert_eq!(app.tick_count, 0);
}
#[tokio::test]
async fn test_dispatch_by_add_series_search_results_block() {
let (mut app, mut sync_network_rx) = construct_app_unit();
app.data.sonarr_data.add_series_search = Some("test search".into());
app
.dispatch_by_sonarr_block(&ActiveSonarrBlock::AddSeriesSearchResults)
.await;
assert!(app.is_loading);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::SearchNewSeries("test search".into()).into()
);
assert!(!app.data.sonarr_data.prompt_confirm);
assert_eq!(app.tick_count, 0);
}
#[tokio::test]
async fn test_check_for_sonarr_prompt_action_no_prompt_confirm() {
let mut app = App::default();
app.data.sonarr_data.prompt_confirm = false;
app.check_for_sonarr_prompt_action().await;
assert!(!app.data.sonarr_data.prompt_confirm);
assert!(!app.should_refresh);
}
#[tokio::test]
async fn test_check_for_sonarr_prompt_action() {
let (mut app, mut sync_network_rx) = construct_app_unit();
app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::GetStatus);
app.check_for_sonarr_prompt_action().await;
assert!(!app.data.sonarr_data.prompt_confirm);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetStatus.into()
);
assert!(app.should_refresh);
assert_eq!(app.data.sonarr_data.prompt_confirm_action, None);
}
#[tokio::test]
async fn test_sonarr_refresh_metadata() {
let (mut app, mut sync_network_rx) = construct_app_unit();
app.is_routing = true;
app.refresh_sonarr_metadata().await;
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetQualityProfiles.into()
);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetLanguageProfiles.into()
);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetTags.into()
);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetRootFolders.into()
);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetDownloads.into()
);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetDiskSpace.into()
);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetStatus.into()
);
assert!(app.is_loading);
}
#[tokio::test]
async fn test_sonarr_on_tick_first_render() {
let (mut app, mut sync_network_rx) = construct_app_unit();
app.is_first_render = true;
app.sonarr_on_tick(ActiveSonarrBlock::Downloads).await;
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetQualityProfiles.into()
);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetLanguageProfiles.into()
);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetTags.into()
);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetRootFolders.into()
);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetDownloads.into()
);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetDiskSpace.into()
);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetStatus.into()
);
assert!(app.is_loading);
assert!(!app.data.sonarr_data.prompt_confirm);
assert!(!app.is_first_render);
}
#[tokio::test]
async fn test_sonarr_on_tick_routing() {
let (mut app, mut sync_network_rx) = construct_app_unit();
app.is_routing = true;
app.should_refresh = true;
app.sonarr_on_tick(ActiveSonarrBlock::Downloads).await;
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetDownloads.into()
);
assert!(!app.data.sonarr_data.prompt_confirm);
}
#[tokio::test]
async fn test_sonarr_on_tick_routing_while_long_request_is_running_should_cancel_request() {
let (mut app, _) = construct_app_unit();
app.is_routing = true;
app.should_refresh = false;
app.sonarr_on_tick(ActiveSonarrBlock::Downloads).await;
assert!(app.cancellation_token.is_cancelled());
}
#[tokio::test]
async fn test_sonarr_on_tick_should_refresh() {
let (mut app, mut sync_network_rx) = construct_app_unit();
app.should_refresh = true;
app.sonarr_on_tick(ActiveSonarrBlock::Downloads).await;
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetDownloads.into()
);
assert!(app.should_refresh);
assert!(!app.data.sonarr_data.prompt_confirm);
}
#[tokio::test]
async fn test_sonarr_on_tick_should_refresh_does_not_cancel_prompt_requests() {
let (mut app, mut sync_network_rx) = construct_app_unit();
app.is_loading = true;
app.is_routing = true;
app.should_refresh = true;
app.sonarr_on_tick(ActiveSonarrBlock::Downloads).await;
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetDownloads.into()
);
assert!(app.is_loading);
assert!(app.should_refresh);
assert!(!app.data.sonarr_data.prompt_confirm);
assert!(!app.cancellation_token.is_cancelled());
}
#[tokio::test]
async fn test_sonarr_on_tick_network_tick_frequency() {
let (mut app, mut sync_network_rx) = construct_app_unit();
app.tick_count = 2;
app.tick_until_poll = 2;
app.sonarr_on_tick(ActiveSonarrBlock::Downloads).await;
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetQualityProfiles.into()
);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetLanguageProfiles.into()
);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetTags.into()
);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetRootFolders.into()
);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetDownloads.into()
);
assert!(app.is_loading);
}
#[tokio::test]
async fn test_populate_seasons_table_unfiltered() {
let mut app = App::default();
app.data.sonarr_data.series.set_items(vec![Series {
seasons: Some(vec![Season::default()]),
..Series::default()
}]);
app.populate_seasons_table().await;
assert!(!app.data.sonarr_data.seasons.items.is_empty());
assert_str_eq!(
app.data.sonarr_data.seasons.items[0]
.title
.as_ref()
.unwrap(),
"Season 0"
);
}
#[tokio::test]
async fn test_populate_seasons_table_filtered() {
let mut app = App::default();
app.data.sonarr_data.series.set_filtered_items(vec![Series {
seasons: Some(vec![Season::default()]),
..Series::default()
}]);
app.populate_seasons_table().await;
assert!(!app.data.sonarr_data.seasons.items.is_empty());
assert_str_eq!(
app.data.sonarr_data.seasons.items[0]
.title
.as_ref()
.unwrap(),
"Season 0"
);
}
#[tokio::test]
async fn test_extract_episode_id() {
let mut app = App::default();
let mut season_details_modal = SeasonDetailsModal::default();
season_details_modal.episodes.set_items(vec![Episode {
id: 1,
..Episode::default()
}]);
app.data.sonarr_data.season_details_modal = Some(season_details_modal);
assert_eq!(app.extract_episode_id().await, 1);
}
#[tokio::test]
#[should_panic(expected = "Season details have not been loaded")]
async fn test_extract_episode_id_requires_season_details_modal_to_be_some() {
let app = App::default();
assert_eq!(app.extract_episode_id().await, 0);
}
#[tokio::test]
async fn test_extract_series_id() {
let mut app = App::default();
app.data.sonarr_data.series.set_items(vec![Series {
id: 1,
..Series::default()
}]);
assert_eq!(app.extract_series_id().await, 1);
}
#[tokio::test]
async fn test_extract_series_id_season_number_tuple() {
let mut app = App::default();
app.data.sonarr_data.series.set_items(vec![Series {
id: 1,
..Series::default()
}]);
app.data.sonarr_data.seasons.set_items(vec![Season {
season_number: 1,
..Season::default()
}]);
assert_eq!(app.extract_series_id_season_number_tuple().await, (1, 1));
}
#[tokio::test]
async fn test_extract_add_new_series_search_query() {
let mut app = App::default();
app.data.sonarr_data.add_series_search = Some("test search".into());
assert_str_eq!(
app.extract_add_new_series_search_query().await,
"test search"
);
}
#[tokio::test]
#[should_panic(expected = "Add series search is empty")]
async fn test_extract_add_new_series_search_query_panics_when_the_query_is_not_set() {
let app = App::default();
app.extract_add_new_series_search_query().await;
}
#[tokio::test]
async fn test_extract_sonarr_indexer_id() {
let mut app = App::default();
app.data.sonarr_data.indexers.set_items(vec![Indexer {
id: 1,
..Indexer::default()
}]);
assert_eq!(app.extract_sonarr_indexer_id().await, 1);
}
fn construct_app_unit<'a>() -> (App<'a>, mpsc::Receiver<NetworkEvent>) {
let (sync_network_tx, sync_network_rx) = mpsc::channel::<NetworkEvent>(500);
let mut app = App {
network_tx: Some(sync_network_tx),
tick_count: 1,
is_first_render: false,
..App::default()
};
app.data.sonarr_data.prompt_confirm = true;
(app, sync_network_rx)
}
}
}
+8 -4
View File
@@ -4,6 +4,8 @@ use anyhow::Result;
use clap::{arg, command, ArgAction, Subcommand}; use clap::{arg, command, ArgAction, Subcommand};
use tokio::sync::Mutex; use tokio::sync::Mutex;
use super::RadarrCommand;
use crate::models::servarr_models::AddRootFolderBody;
use crate::{ use crate::{
app::App, app::App,
cli::{CliCommandHandler, Command}, cli::{CliCommandHandler, Command},
@@ -11,8 +13,6 @@ use crate::{
network::{radarr_network::RadarrEvent, NetworkTrait}, network::{radarr_network::RadarrEvent, NetworkTrait},
}; };
use super::RadarrCommand;
#[cfg(test)] #[cfg(test)]
#[path = "add_command_handler_tests.rs"] #[path = "add_command_handler_tests.rs"]
mod add_command_handler_tests; mod add_command_handler_tests;
@@ -125,6 +125,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrAddCommand> for RadarrAddCommandHan
minimum_availability: minimum_availability.to_string(), minimum_availability: minimum_availability.to_string(),
monitored: !disable_monitoring, monitored: !disable_monitoring,
tags, tags,
tag_input_string: None,
add_options: AddMovieOptions { add_options: AddMovieOptions {
monitor: monitor.to_string(), monitor: monitor.to_string(),
search_for_movie: !no_search_for_movie, search_for_movie: !no_search_for_movie,
@@ -132,14 +133,17 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrAddCommand> for RadarrAddCommandHan
}; };
let resp = self let resp = self
.network .network
.handle_network_event(RadarrEvent::AddMovie(Some(body)).into()) .handle_network_event(RadarrEvent::AddMovie(body).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
RadarrAddCommand::RootFolder { root_folder_path } => { RadarrAddCommand::RootFolder { root_folder_path } => {
let add_root_folder_body = AddRootFolderBody {
path: root_folder_path,
};
let resp = self let resp = self
.network .network
.handle_network_event(RadarrEvent::AddRootFolder(Some(root_folder_path)).into()) .handle_network_event(RadarrEvent::AddRootFolder(add_root_folder_body).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
+7 -2
View File
@@ -368,6 +368,7 @@ mod tests {
use super::*; use super::*;
use mockall::predicate::eq; use mockall::predicate::eq;
use crate::models::servarr_models::AddRootFolderBody;
use serde_json::json; use serde_json::json;
use tokio::sync::Mutex; use tokio::sync::Mutex;
@@ -381,6 +382,7 @@ mod tests {
minimum_availability: "released".to_owned(), minimum_availability: "released".to_owned(),
monitored: false, monitored: false,
tags: vec![1, 2], tags: vec![1, 2],
tag_input_string: None,
add_options: AddMovieOptions { add_options: AddMovieOptions {
monitor: "movieAndCollection".to_owned(), monitor: "movieAndCollection".to_owned(),
search_for_movie: false, search_for_movie: false,
@@ -390,7 +392,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::AddMovie(Some(expected_add_movie_body)).into(), RadarrEvent::AddMovie(expected_add_movie_body).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -420,11 +422,14 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn test_handle_add_root_folder_command() { async fn test_handle_add_root_folder_command() {
let expected_root_folder_path = "/nfs/test".to_owned(); let expected_root_folder_path = "/nfs/test".to_owned();
let expected_add_root_folder_body = AddRootFolderBody {
path: expected_root_folder_path.clone(),
};
let mut mock_network = MockNetworkTrait::new(); let mut mock_network = MockNetworkTrait::new();
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::AddRootFolder(Some(expected_root_folder_path.clone())).into(), RadarrEvent::AddRootFolder(expected_add_root_folder_body).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
+5 -5
View File
@@ -89,21 +89,21 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrDeleteCommand> for RadarrDeleteComm
RadarrDeleteCommand::BlocklistItem { blocklist_item_id } => { RadarrDeleteCommand::BlocklistItem { blocklist_item_id } => {
let resp = self let resp = self
.network .network
.handle_network_event(RadarrEvent::DeleteBlocklistItem(Some(blocklist_item_id)).into()) .handle_network_event(RadarrEvent::DeleteBlocklistItem(blocklist_item_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
RadarrDeleteCommand::Download { download_id } => { RadarrDeleteCommand::Download { download_id } => {
let resp = self let resp = self
.network .network
.handle_network_event(RadarrEvent::DeleteDownload(Some(download_id)).into()) .handle_network_event(RadarrEvent::DeleteDownload(download_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
RadarrDeleteCommand::Indexer { indexer_id } => { RadarrDeleteCommand::Indexer { indexer_id } => {
let resp = self let resp = self
.network .network
.handle_network_event(RadarrEvent::DeleteIndexer(Some(indexer_id)).into()) .handle_network_event(RadarrEvent::DeleteIndexer(indexer_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
@@ -119,14 +119,14 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrDeleteCommand> for RadarrDeleteComm
}; };
let resp = self let resp = self
.network .network
.handle_network_event(RadarrEvent::DeleteMovie(Some(delete_movie_params)).into()) .handle_network_event(RadarrEvent::DeleteMovie(delete_movie_params).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
RadarrDeleteCommand::RootFolder { root_folder_id } => { RadarrDeleteCommand::RootFolder { root_folder_id } => {
let resp = self let resp = self
.network .network
.handle_network_event(RadarrEvent::DeleteRootFolder(Some(root_folder_id)).into()) .handle_network_event(RadarrEvent::DeleteRootFolder(root_folder_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
@@ -268,7 +268,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::DeleteBlocklistItem(Some(expected_blocklist_item_id)).into(), RadarrEvent::DeleteBlocklistItem(expected_blocklist_item_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -299,7 +299,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::DeleteDownload(Some(expected_download_id)).into(), RadarrEvent::DeleteDownload(expected_download_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -325,7 +325,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::DeleteIndexer(Some(expected_indexer_id)).into(), RadarrEvent::DeleteIndexer(expected_indexer_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -355,7 +355,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::DeleteMovie(Some(expected_delete_movie_params)).into(), RadarrEvent::DeleteMovie(expected_delete_movie_params).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -385,7 +385,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::DeleteRootFolder(Some(expected_root_folder_id)).into(), RadarrEvent::DeleteRootFolder(expected_root_folder_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
+6 -4
View File
@@ -390,7 +390,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrEditCommand> for RadarrEditCommandH
}; };
self self
.network .network
.handle_network_event(RadarrEvent::EditAllIndexerSettings(Some(params)).into()) .handle_network_event(RadarrEvent::EditAllIndexerSettings(params).into())
.await?; .await?;
"All indexer settings updated".to_owned() "All indexer settings updated".to_owned()
} else { } else {
@@ -420,7 +420,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrEditCommand> for RadarrEditCommandH
}; };
self self
.network .network
.handle_network_event(RadarrEvent::EditCollection(Some(edit_collection_params)).into()) .handle_network_event(RadarrEvent::EditCollection(edit_collection_params).into())
.await?; .await?;
"Collection updated".to_owned() "Collection updated".to_owned()
} }
@@ -455,13 +455,14 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrEditCommand> for RadarrEditCommandH
api_key, api_key,
seed_ratio, seed_ratio,
tags: tag, tags: tag,
tag_input_string: None,
priority, priority,
clear_tags, clear_tags,
}; };
self self
.network .network
.handle_network_event(RadarrEvent::EditIndexer(Some(edit_indexer_params)).into()) .handle_network_event(RadarrEvent::EditIndexer(edit_indexer_params).into())
.await?; .await?;
"Indexer updated".to_owned() "Indexer updated".to_owned()
} }
@@ -483,12 +484,13 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrEditCommand> for RadarrEditCommandH
quality_profile_id, quality_profile_id,
root_folder_path, root_folder_path,
tags: tag, tags: tag,
tag_input_string: None,
clear_tags, clear_tags,
}; };
self self
.network .network
.handle_network_event(RadarrEvent::EditMovie(Some(edit_movie_params)).into()) .handle_network_event(RadarrEvent::EditMovie(edit_movie_params).into())
.await?; .await?;
"Movie Updated".to_owned() "Movie Updated".to_owned()
} }
+18 -12
View File
@@ -857,7 +857,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::EditAllIndexerSettings(Some(expected_edit_all_indexer_settings)).into(), RadarrEvent::EditAllIndexerSettings(expected_edit_all_indexer_settings).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -928,7 +928,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::EditAllIndexerSettings(Some(expected_edit_all_indexer_settings)).into(), RadarrEvent::EditAllIndexerSettings(expected_edit_all_indexer_settings).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -1000,7 +1000,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::EditAllIndexerSettings(Some(expected_edit_all_indexer_settings)).into(), RadarrEvent::EditAllIndexerSettings(expected_edit_all_indexer_settings).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -1047,7 +1047,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::EditCollection(Some(expected_edit_collection_params)).into(), RadarrEvent::EditCollection(expected_edit_collection_params).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -1089,7 +1089,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::EditCollection(Some(expected_edit_collection_params)).into(), RadarrEvent::EditCollection(expected_edit_collection_params).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -1131,7 +1131,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::EditCollection(Some(expected_edit_collection_params)).into(), RadarrEvent::EditCollection(expected_edit_collection_params).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -1171,6 +1171,7 @@ mod tests {
api_key: Some("testKey".to_owned()), api_key: Some("testKey".to_owned()),
seed_ratio: Some("1.2".to_owned()), seed_ratio: Some("1.2".to_owned()),
tags: Some(vec![1, 2]), tags: Some(vec![1, 2]),
tag_input_string: None,
priority: Some(25), priority: Some(25),
clear_tags: false, clear_tags: false,
}; };
@@ -1178,7 +1179,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::EditIndexer(Some(expected_edit_indexer_params)).into(), RadarrEvent::EditIndexer(expected_edit_indexer_params).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -1224,6 +1225,7 @@ mod tests {
api_key: Some("testKey".to_owned()), api_key: Some("testKey".to_owned()),
seed_ratio: Some("1.2".to_owned()), seed_ratio: Some("1.2".to_owned()),
tags: Some(vec![1, 2]), tags: Some(vec![1, 2]),
tag_input_string: None,
priority: Some(25), priority: Some(25),
clear_tags: false, clear_tags: false,
}; };
@@ -1231,7 +1233,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::EditIndexer(Some(expected_edit_indexer_params)).into(), RadarrEvent::EditIndexer(expected_edit_indexer_params).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -1277,6 +1279,7 @@ mod tests {
api_key: Some("testKey".to_owned()), api_key: Some("testKey".to_owned()),
seed_ratio: Some("1.2".to_owned()), seed_ratio: Some("1.2".to_owned()),
tags: Some(vec![1, 2]), tags: Some(vec![1, 2]),
tag_input_string: None,
priority: Some(25), priority: Some(25),
clear_tags: false, clear_tags: false,
}; };
@@ -1284,7 +1287,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::EditIndexer(Some(expected_edit_indexer_params)).into(), RadarrEvent::EditIndexer(expected_edit_indexer_params).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -1327,13 +1330,14 @@ mod tests {
quality_profile_id: Some(1), quality_profile_id: Some(1),
root_folder_path: Some("/nfs/test".to_owned()), root_folder_path: Some("/nfs/test".to_owned()),
tags: Some(vec![1, 2]), tags: Some(vec![1, 2]),
tag_input_string: None,
clear_tags: false, clear_tags: false,
}; };
let mut mock_network = MockNetworkTrait::new(); let mut mock_network = MockNetworkTrait::new();
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::EditMovie(Some(expected_edit_movie_params)).into(), RadarrEvent::EditMovie(expected_edit_movie_params).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -1369,13 +1373,14 @@ mod tests {
quality_profile_id: Some(1), quality_profile_id: Some(1),
root_folder_path: Some("/nfs/test".to_owned()), root_folder_path: Some("/nfs/test".to_owned()),
tags: Some(vec![1, 2]), tags: Some(vec![1, 2]),
tag_input_string: None,
clear_tags: false, clear_tags: false,
}; };
let mut mock_network = MockNetworkTrait::new(); let mut mock_network = MockNetworkTrait::new();
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::EditMovie(Some(expected_edit_movie_params)).into(), RadarrEvent::EditMovie(expected_edit_movie_params).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -1411,13 +1416,14 @@ mod tests {
quality_profile_id: Some(1), quality_profile_id: Some(1),
root_folder_path: Some("/nfs/test".to_owned()), root_folder_path: Some("/nfs/test".to_owned()),
tags: Some(vec![1, 2]), tags: Some(vec![1, 2]),
tag_input_string: None,
clear_tags: false, clear_tags: false,
}; };
let mut mock_network = MockNetworkTrait::new(); let mut mock_network = MockNetworkTrait::new();
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::EditMovie(Some(expected_edit_movie_params)).into(), RadarrEvent::EditMovie(expected_edit_movie_params).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
+2 -2
View File
@@ -90,14 +90,14 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrGetCommand> for RadarrGetCommandHan
RadarrGetCommand::MovieDetails { movie_id } => { RadarrGetCommand::MovieDetails { movie_id } => {
let resp = self let resp = self
.network .network
.handle_network_event(RadarrEvent::GetMovieDetails(Some(movie_id)).into()) .handle_network_event(RadarrEvent::GetMovieDetails(movie_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
RadarrGetCommand::MovieHistory { movie_id } => { RadarrGetCommand::MovieHistory { movie_id } => {
let resp = self let resp = self
.network .network
.handle_network_event(RadarrEvent::GetMovieHistory(Some(movie_id)).into()) .handle_network_event(RadarrEvent::GetMovieHistory(movie_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
+2 -2
View File
@@ -182,7 +182,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::GetMovieDetails(Some(expected_movie_id)).into(), RadarrEvent::GetMovieDetails(expected_movie_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -208,7 +208,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::GetMovieHistory(Some(expected_movie_id)).into(), RadarrEvent::GetMovieHistory(expected_movie_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
+2 -2
View File
@@ -131,7 +131,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrListCommand> for RadarrListCommandH
} => { } => {
let logs = self let logs = self
.network .network
.handle_network_event(RadarrEvent::GetLogs(Some(events)).into()) .handle_network_event(RadarrEvent::GetLogs(events).into())
.await?; .await?;
if output_in_log_format { if output_in_log_format {
@@ -152,7 +152,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrListCommand> for RadarrListCommandH
RadarrListCommand::MovieCredits { movie_id } => { RadarrListCommand::MovieCredits { movie_id } => {
let resp = self let resp = self
.network .network
.handle_network_event(RadarrEvent::GetMovieCredits(Some(movie_id)).into()) .handle_network_event(RadarrEvent::GetMovieCredits(movie_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
+2 -2
View File
@@ -163,7 +163,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::GetMovieCredits(Some(expected_movie_id)).into(), RadarrEvent::GetMovieCredits(expected_movie_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -189,7 +189,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::GetLogs(Some(expected_events)).into(), RadarrEvent::GetLogs(expected_events).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
+6 -6
View File
@@ -209,7 +209,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrCommand> for RadarrCliHandler<'a, '
}; };
let resp = self let resp = self
.network .network
.handle_network_event(RadarrEvent::DownloadRelease(Some(params)).into()) .handle_network_event(RadarrEvent::DownloadRelease(params).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
@@ -217,28 +217,28 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrCommand> for RadarrCliHandler<'a, '
println!("Searching for releases. This may take a minute..."); println!("Searching for releases. This may take a minute...");
let resp = self let resp = self
.network .network
.handle_network_event(RadarrEvent::GetReleases(Some(movie_id)).into()) .handle_network_event(RadarrEvent::GetReleases(movie_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
RadarrCommand::SearchNewMovie { query } => { RadarrCommand::SearchNewMovie { query } => {
let resp = self let resp = self
.network .network
.handle_network_event(RadarrEvent::SearchNewMovie(Some(query)).into()) .handle_network_event(RadarrEvent::SearchNewMovie(query).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
RadarrCommand::StartTask { task_name } => { RadarrCommand::StartTask { task_name } => {
let resp = self let resp = self
.network .network
.handle_network_event(RadarrEvent::StartTask(Some(task_name)).into()) .handle_network_event(RadarrEvent::StartTask(task_name).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
RadarrCommand::TestIndexer { indexer_id } => { RadarrCommand::TestIndexer { indexer_id } => {
let resp = self let resp = self
.network .network
.handle_network_event(RadarrEvent::TestIndexer(Some(indexer_id)).into()) .handle_network_event(RadarrEvent::TestIndexer(indexer_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
@@ -253,7 +253,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrCommand> for RadarrCliHandler<'a, '
RadarrCommand::TriggerAutomaticSearch { movie_id } => { RadarrCommand::TriggerAutomaticSearch { movie_id } => {
let resp = self let resp = self
.network .network
.handle_network_event(RadarrEvent::TriggerAutomaticSearch(Some(movie_id)).into()) .handle_network_event(RadarrEvent::TriggerAutomaticSearch(movie_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
+12 -12
View File
@@ -293,9 +293,9 @@ mod tests {
))) )))
}); });
let app_arc = Arc::new(Mutex::new(App::default())); let app_arc = Arc::new(Mutex::new(App::default()));
let claer_blocklist_command = RadarrCommand::ClearBlocklist; let clear_blocklist_command = RadarrCommand::ClearBlocklist;
let result = RadarrCliHandler::with(&app_arc, claer_blocklist_command, &mut mock_network) let result = RadarrCliHandler::with(&app_arc, clear_blocklist_command, &mut mock_network)
.handle() .handle()
.await; .await;
@@ -313,7 +313,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::DownloadRelease(Some(expected_release_download_body)).into(), RadarrEvent::DownloadRelease(expected_release_download_body).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -342,7 +342,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::GetReleases(Some(expected_movie_id)).into(), RadarrEvent::GetReleases(expected_movie_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -367,7 +367,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::SearchNewMovie(Some(expected_search_query)).into(), RadarrEvent::SearchNewMovie(expected_search_query).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -394,7 +394,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::StartTask(Some(expected_task_name)).into(), RadarrEvent::StartTask(expected_task_name).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -421,7 +421,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::TestIndexer(Some(expected_indexer_id)).into(), RadarrEvent::TestIndexer(expected_indexer_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -468,7 +468,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::TriggerAutomaticSearch(Some(expected_movie_id)).into(), RadarrEvent::TriggerAutomaticSearch(expected_movie_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -524,7 +524,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::DeleteBlocklistItem(Some(expected_blocklist_item_id)).into(), RadarrEvent::DeleteBlocklistItem(expected_blocklist_item_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -584,7 +584,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::EditAllIndexerSettings(Some(expected_edit_all_indexer_settings)).into(), RadarrEvent::EditAllIndexerSettings(expected_edit_all_indexer_settings).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -654,7 +654,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::GetMovieCredits(Some(expected_movie_id)).into(), RadarrEvent::GetMovieCredits(expected_movie_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -680,7 +680,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::UpdateAndScan(Some(expected_movie_id)).into(), RadarrEvent::UpdateAndScan(expected_movie_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
+1 -1
View File
@@ -88,7 +88,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrRefreshCommand>
RadarrRefreshCommand::Movie { movie_id } => { RadarrRefreshCommand::Movie { movie_id } => {
let resp = self let resp = self
.network .network
.handle_network_event(RadarrEvent::UpdateAndScan(Some(movie_id)).into()) .handle_network_event(RadarrEvent::UpdateAndScan(movie_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
@@ -112,7 +112,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::UpdateAndScan(Some(expected_movie_id)).into(), RadarrEvent::UpdateAndScan(expected_movie_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
+8 -4
View File
@@ -4,6 +4,8 @@ use anyhow::Result;
use clap::{ArgAction, Subcommand}; use clap::{ArgAction, Subcommand};
use tokio::sync::Mutex; use tokio::sync::Mutex;
use super::SonarrCommand;
use crate::models::servarr_models::AddRootFolderBody;
use crate::{ use crate::{
app::App, app::App,
cli::{CliCommandHandler, Command}, cli::{CliCommandHandler, Command},
@@ -11,8 +13,6 @@ use crate::{
network::{sonarr_network::SonarrEvent, NetworkTrait}, network::{sonarr_network::SonarrEvent, NetworkTrait},
}; };
use super::SonarrCommand;
#[cfg(test)] #[cfg(test)]
#[path = "add_command_handler_tests.rs"] #[path = "add_command_handler_tests.rs"]
mod add_command_handler_tests; mod add_command_handler_tests;
@@ -140,6 +140,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrAddCommand> for SonarrAddCommandHan
series_type: series_type.to_string(), series_type: series_type.to_string(),
season_folder: !disable_season_folders, season_folder: !disable_season_folders,
tags, tags,
tag_input_string: None,
add_options: AddSeriesOptions { add_options: AddSeriesOptions {
monitor: monitor.to_string(), monitor: monitor.to_string(),
search_for_cutoff_unmet_episodes: !no_search_for_series, search_for_cutoff_unmet_episodes: !no_search_for_series,
@@ -148,14 +149,17 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrAddCommand> for SonarrAddCommandHan
}; };
let resp = self let resp = self
.network .network
.handle_network_event(SonarrEvent::AddSeries(Some(body)).into()) .handle_network_event(SonarrEvent::AddSeries(body).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
SonarrAddCommand::RootFolder { root_folder_path } => { SonarrAddCommand::RootFolder { root_folder_path } => {
let add_root_folder_body = AddRootFolderBody {
path: root_folder_path,
};
let resp = self let resp = self
.network .network
.handle_network_event(SonarrEvent::AddRootFolder(Some(root_folder_path)).into()) .handle_network_event(SonarrEvent::AddRootFolder(add_root_folder_body).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
+7 -2
View File
@@ -469,17 +469,21 @@ mod tests {
use super::*; use super::*;
use mockall::predicate::eq; use mockall::predicate::eq;
use crate::models::servarr_models::AddRootFolderBody;
use serde_json::json; use serde_json::json;
use tokio::sync::Mutex; use tokio::sync::Mutex;
#[tokio::test] #[tokio::test]
async fn test_handle_add_root_folder_command() { async fn test_handle_add_root_folder_command() {
let expected_root_folder_path = "/nfs/test".to_owned(); let expected_root_folder_path = "/nfs/test".to_owned();
let expected_add_root_folder_body = AddRootFolderBody {
path: expected_root_folder_path.clone(),
};
let mut mock_network = MockNetworkTrait::new(); let mut mock_network = MockNetworkTrait::new();
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::AddRootFolder(Some(expected_root_folder_path.clone())).into(), SonarrEvent::AddRootFolder(expected_add_root_folder_body.clone()).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -511,6 +515,7 @@ mod tests {
series_type: "anime".to_owned(), series_type: "anime".to_owned(),
monitored: false, monitored: false,
tags: vec![1, 2], tags: vec![1, 2],
tag_input_string: None,
season_folder: false, season_folder: false,
add_options: AddSeriesOptions { add_options: AddSeriesOptions {
monitor: "future".to_owned(), monitor: "future".to_owned(),
@@ -522,7 +527,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::AddSeries(Some(expected_add_series_body)).into(), SonarrEvent::AddSeries(expected_add_series_body).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
+6 -6
View File
@@ -94,35 +94,35 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrDeleteCommand> for SonarrDeleteComm
SonarrDeleteCommand::BlocklistItem { blocklist_item_id } => { SonarrDeleteCommand::BlocklistItem { blocklist_item_id } => {
let resp = self let resp = self
.network .network
.handle_network_event(SonarrEvent::DeleteBlocklistItem(Some(blocklist_item_id)).into()) .handle_network_event(SonarrEvent::DeleteBlocklistItem(blocklist_item_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
SonarrDeleteCommand::Download { download_id } => { SonarrDeleteCommand::Download { download_id } => {
let resp = self let resp = self
.network .network
.handle_network_event(SonarrEvent::DeleteDownload(Some(download_id)).into()) .handle_network_event(SonarrEvent::DeleteDownload(download_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
SonarrDeleteCommand::EpisodeFile { episode_file_id } => { SonarrDeleteCommand::EpisodeFile { episode_file_id } => {
let resp = self let resp = self
.network .network
.handle_network_event(SonarrEvent::DeleteEpisodeFile(Some(episode_file_id)).into()) .handle_network_event(SonarrEvent::DeleteEpisodeFile(episode_file_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
SonarrDeleteCommand::Indexer { indexer_id } => { SonarrDeleteCommand::Indexer { indexer_id } => {
let resp = self let resp = self
.network .network
.handle_network_event(SonarrEvent::DeleteIndexer(Some(indexer_id)).into()) .handle_network_event(SonarrEvent::DeleteIndexer(indexer_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
SonarrDeleteCommand::RootFolder { root_folder_id } => { SonarrDeleteCommand::RootFolder { root_folder_id } => {
let resp = self let resp = self
.network .network
.handle_network_event(SonarrEvent::DeleteRootFolder(Some(root_folder_id)).into()) .handle_network_event(SonarrEvent::DeleteRootFolder(root_folder_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
@@ -138,7 +138,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrDeleteCommand> for SonarrDeleteComm
}; };
let resp = self let resp = self
.network .network
.handle_network_event(SonarrEvent::DeleteSeries(Some(delete_series_params)).into()) .handle_network_event(SonarrEvent::DeleteSeries(delete_series_params).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
+31 -5
View File
@@ -301,7 +301,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::DeleteBlocklistItem(Some(expected_blocklist_item_id)).into(), SonarrEvent::DeleteBlocklistItem(expected_blocklist_item_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -332,7 +332,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::DeleteDownload(Some(expected_download_id)).into(), SonarrEvent::DeleteDownload(expected_download_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -351,6 +351,32 @@ mod tests {
assert!(result.is_ok()); assert!(result.is_ok());
} }
#[tokio::test]
async fn test_handle_delete_episode_file_command() {
let expected_episode_file_id = 1;
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(
SonarrEvent::DeleteEpisodeFile(expected_episode_file_id).into(),
))
.times(1)
.returning(|_| {
Ok(Serdeable::Sonarr(SonarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::default()));
let delete_episode_file_command = SonarrDeleteCommand::EpisodeFile { episode_file_id: 1 };
let result =
SonarrDeleteCommandHandler::with(&app_arc, delete_episode_file_command, &mut mock_network)
.handle()
.await;
assert!(result.is_ok());
}
#[tokio::test] #[tokio::test]
async fn test_handle_delete_indexer_command() { async fn test_handle_delete_indexer_command() {
let expected_indexer_id = 1; let expected_indexer_id = 1;
@@ -358,7 +384,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::DeleteIndexer(Some(expected_indexer_id)).into(), SonarrEvent::DeleteIndexer(expected_indexer_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -384,7 +410,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::DeleteRootFolder(Some(expected_root_folder_id)).into(), SonarrEvent::DeleteRootFolder(expected_root_folder_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -414,7 +440,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::DeleteSeries(Some(expected_delete_series_params)).into(), SonarrEvent::DeleteSeries(expected_delete_series_params).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
+5 -3
View File
@@ -274,7 +274,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrEditCommand> for SonarrEditCommandH
}; };
self self
.network .network
.handle_network_event(SonarrEvent::EditAllIndexerSettings(Some(params)).into()) .handle_network_event(SonarrEvent::EditAllIndexerSettings(params).into())
.await?; .await?;
"All indexer settings updated".to_owned() "All indexer settings updated".to_owned()
} else { } else {
@@ -312,13 +312,14 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrEditCommand> for SonarrEditCommandH
api_key, api_key,
seed_ratio, seed_ratio,
tags: tag, tags: tag,
tag_input_string: None,
priority, priority,
clear_tags, clear_tags,
}; };
self self
.network .network
.handle_network_event(SonarrEvent::EditIndexer(Some(edit_indexer_params)).into()) .handle_network_event(SonarrEvent::EditIndexer(edit_indexer_params).into())
.await?; .await?;
"Indexer updated".to_owned() "Indexer updated".to_owned()
} }
@@ -347,12 +348,13 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrEditCommand> for SonarrEditCommandH
language_profile_id, language_profile_id,
root_folder_path, root_folder_path,
tags: tag, tags: tag,
tag_input_string: None,
clear_tags, clear_tags,
}; };
self self
.network .network
.handle_network_event(SonarrEvent::EditSeries(Some(edit_series_params)).into()) .handle_network_event(SonarrEvent::EditSeries(edit_series_params).into())
.await?; .await?;
"Series Updated".to_owned() "Series Updated".to_owned()
} }
+9 -5
View File
@@ -650,7 +650,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::EditAllIndexerSettings(Some(expected_edit_all_indexer_settings)).into(), SonarrEvent::EditAllIndexerSettings(expected_edit_all_indexer_settings).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -689,6 +689,7 @@ mod tests {
api_key: Some("testKey".to_owned()), api_key: Some("testKey".to_owned()),
seed_ratio: Some("1.2".to_owned()), seed_ratio: Some("1.2".to_owned()),
tags: Some(vec![1, 2]), tags: Some(vec![1, 2]),
tag_input_string: None,
priority: Some(25), priority: Some(25),
clear_tags: false, clear_tags: false,
}; };
@@ -696,7 +697,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::EditIndexer(Some(expected_edit_indexer_params)).into(), SonarrEvent::EditIndexer(expected_edit_indexer_params).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -741,13 +742,14 @@ mod tests {
language_profile_id: Some(1), language_profile_id: Some(1),
root_folder_path: Some("/nfs/test".to_owned()), root_folder_path: Some("/nfs/test".to_owned()),
tags: Some(vec![1, 2]), tags: Some(vec![1, 2]),
tag_input_string: None,
clear_tags: false, clear_tags: false,
}; };
let mut mock_network = MockNetworkTrait::new(); let mut mock_network = MockNetworkTrait::new();
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::EditSeries(Some(expected_edit_series_params)).into(), SonarrEvent::EditSeries(expected_edit_series_params).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -788,13 +790,14 @@ mod tests {
language_profile_id: Some(1), language_profile_id: Some(1),
root_folder_path: Some("/nfs/test".to_owned()), root_folder_path: Some("/nfs/test".to_owned()),
tags: Some(vec![1, 2]), tags: Some(vec![1, 2]),
tag_input_string: None,
clear_tags: false, clear_tags: false,
}; };
let mut mock_network = MockNetworkTrait::new(); let mut mock_network = MockNetworkTrait::new();
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::EditSeries(Some(expected_edit_series_params)).into(), SonarrEvent::EditSeries(expected_edit_series_params).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -835,13 +838,14 @@ mod tests {
language_profile_id: Some(1), language_profile_id: Some(1),
root_folder_path: Some("/nfs/test".to_owned()), root_folder_path: Some("/nfs/test".to_owned()),
tags: Some(vec![1, 2]), tags: Some(vec![1, 2]),
tag_input_string: None,
clear_tags: false, clear_tags: false,
}; };
let mut mock_network = MockNetworkTrait::new(); let mut mock_network = MockNetworkTrait::new();
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::EditSeries(Some(expected_edit_series_params)).into(), SonarrEvent::EditSeries(expected_edit_series_params).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
+2 -2
View File
@@ -83,7 +83,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrGetCommand> for SonarrGetCommandHan
SonarrGetCommand::EpisodeDetails { episode_id } => { SonarrGetCommand::EpisodeDetails { episode_id } => {
let resp = self let resp = self
.network .network
.handle_network_event(SonarrEvent::GetEpisodeDetails(Some(episode_id)).into()) .handle_network_event(SonarrEvent::GetEpisodeDetails(episode_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
@@ -104,7 +104,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrGetCommand> for SonarrGetCommandHan
SonarrGetCommand::SeriesDetails { series_id } => { SonarrGetCommand::SeriesDetails { series_id } => {
let resp = self let resp = self
.network .network
.handle_network_event(SonarrEvent::GetSeriesDetails(Some(series_id)).into()) .handle_network_event(SonarrEvent::GetSeriesDetails(series_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
+2 -2
View File
@@ -160,7 +160,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::GetEpisodeDetails(Some(expected_episode_id)).into(), SonarrEvent::GetEpisodeDetails(expected_episode_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -232,7 +232,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::GetSeriesDetails(Some(expected_series_id)).into(), SonarrEvent::GetSeriesDetails(expected_series_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
+48 -5
View File
@@ -33,6 +33,15 @@ pub enum SonarrListCommand {
)] )]
series_id: i64, series_id: i64,
}, },
#[command(about = "List the episode files for the series with the given ID")]
EpisodeFiles {
#[arg(
long,
help = "The Sonarr ID of the series whose episode files you wish to fetch",
required = true
)]
series_id: i64,
},
#[command(about = "Fetch all history events for the episode with the given ID")] #[command(about = "Fetch all history events for the episode with the given ID")]
EpisodeHistory { EpisodeHistory {
#[arg( #[arg(
@@ -67,6 +76,23 @@ pub enum SonarrListCommand {
QueuedEvents, QueuedEvents,
#[command(about = "List all root folders in Sonarr")] #[command(about = "List all root folders in Sonarr")]
RootFolders, RootFolders,
#[command(
about = "Fetch all history events for the given season corresponding to the series with the given ID."
)]
SeasonHistory {
#[arg(
long,
help = "The Sonarr ID of the series whose history you wish to fetch and list",
required = true
)]
series_id: i64,
#[arg(
long,
help = "The season number to fetch history events for",
required = true
)]
season_number: i64,
},
#[command(about = "List all series in your Sonarr library")] #[command(about = "List all series in your Sonarr library")]
Series, Series,
#[command(about = "Fetch all history events for the series with the given ID")] #[command(about = "Fetch all history events for the series with the given ID")]
@@ -137,21 +163,28 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrListCommand> for SonarrListCommandH
SonarrListCommand::Episodes { series_id } => { SonarrListCommand::Episodes { series_id } => {
let resp = self let resp = self
.network .network
.handle_network_event(SonarrEvent::GetEpisodes(Some(series_id)).into()) .handle_network_event(SonarrEvent::GetEpisodes(series_id).into())
.await?;
serde_json::to_string_pretty(&resp)?
}
SonarrListCommand::EpisodeFiles { series_id } => {
let resp = self
.network
.handle_network_event(SonarrEvent::GetEpisodeFiles(series_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
SonarrListCommand::EpisodeHistory { episode_id } => { SonarrListCommand::EpisodeHistory { episode_id } => {
let resp = self let resp = self
.network .network
.handle_network_event(SonarrEvent::GetEpisodeHistory(Some(episode_id)).into()) .handle_network_event(SonarrEvent::GetEpisodeHistory(episode_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
SonarrListCommand::History { events: items } => { SonarrListCommand::History { events: items } => {
let resp = self let resp = self
.network .network
.handle_network_event(SonarrEvent::GetHistory(Some(items)).into()) .handle_network_event(SonarrEvent::GetHistory(items).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
@@ -175,7 +208,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrListCommand> for SonarrListCommandH
} => { } => {
let logs = self let logs = self
.network .network
.handle_network_event(SonarrEvent::GetLogs(Some(events)).into()) .handle_network_event(SonarrEvent::GetLogs(events).into())
.await?; .await?;
if output_in_log_format { if output_in_log_format {
@@ -207,6 +240,16 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrListCommand> for SonarrListCommandH
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
SonarrListCommand::SeasonHistory {
series_id,
season_number,
} => {
let resp = self
.network
.handle_network_event(SonarrEvent::GetSeasonHistory((series_id, season_number)).into())
.await?;
serde_json::to_string_pretty(&resp)?
}
SonarrListCommand::Series => { SonarrListCommand::Series => {
let resp = self let resp = self
.network .network
@@ -217,7 +260,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrListCommand> for SonarrListCommandH
SonarrListCommand::SeriesHistory { series_id } => { SonarrListCommand::SeriesHistory { series_id } => {
let resp = self let resp = self
.network .network
.handle_network_event(SonarrEvent::GetSeriesHistory(Some(series_id)).into()) .handle_network_event(SonarrEvent::GetSeriesHistory(series_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
+146 -5
View File
@@ -57,6 +57,18 @@ mod tests {
); );
} }
#[test]
fn test_list_episode_files_requires_series_id() {
let result =
Cli::command().try_get_matches_from(["managarr", "sonarr", "list", "episode-files"]);
assert!(result.is_err());
assert_eq!(
result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument
);
}
#[test] #[test]
fn test_list_episode_history_requires_series_id() { fn test_list_episode_history_requires_series_id() {
let result = let result =
@@ -149,6 +161,79 @@ mod tests {
} }
} }
#[test]
fn test_list_episode_files_success() {
let expected_args = SonarrListCommand::EpisodeFiles { series_id: 1 };
let result = Cli::try_parse_from([
"managarr",
"sonarr",
"list",
"episode-files",
"--series-id",
"1",
]);
assert!(result.is_ok());
if let Some(Command::Sonarr(SonarrCommand::List(episode_files_command))) =
result.unwrap().command
{
assert_eq!(episode_files_command, expected_args);
}
}
#[test]
fn test_season_history_requires_series_id() {
let result = Cli::command().try_get_matches_from([
"managarr",
"sonarr",
"list",
"season-history",
"--season-number",
"1",
]);
assert!(result.is_err());
assert_eq!(
result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument
);
}
#[test]
fn test_season_history_requires_season_number() {
let result = Cli::command().try_get_matches_from([
"managarr",
"sonarr",
"list",
"season-history",
"--series-id",
"1",
]);
assert!(result.is_err());
assert_eq!(
result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument
);
}
#[test]
fn test_season_history_requirements_satisfied() {
let result = Cli::command().try_get_matches_from([
"managarr",
"sonarr",
"list",
"season-history",
"--series-id",
"1",
"--season-number",
"1",
]);
assert!(result.is_ok());
}
#[test] #[test]
fn test_list_series_history_requires_series_id() { fn test_list_series_history_requires_series_id() {
let result = let result =
@@ -244,7 +329,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::GetEpisodes(Some(expected_series_id)).into(), SonarrEvent::GetEpisodes(expected_series_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -263,6 +348,32 @@ mod tests {
assert!(result.is_ok()); assert!(result.is_ok());
} }
#[tokio::test]
async fn test_handle_list_episode_files_command() {
let expected_series_id = 1;
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(
SonarrEvent::GetEpisodeFiles(expected_series_id).into(),
))
.times(1)
.returning(|_| {
Ok(Serdeable::Sonarr(SonarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::default()));
let list_episode_files_command = SonarrListCommand::EpisodeFiles { series_id: 1 };
let result =
SonarrListCommandHandler::with(&app_arc, list_episode_files_command, &mut mock_network)
.handle()
.await;
assert!(result.is_ok());
}
#[tokio::test] #[tokio::test]
async fn test_handle_list_history_command() { async fn test_handle_list_history_command() {
let expected_events = 1000; let expected_events = 1000;
@@ -270,7 +381,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::GetHistory(Some(expected_events)).into(), SonarrEvent::GetHistory(expected_events).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -296,7 +407,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::GetLogs(Some(expected_events)).into(), SonarrEvent::GetLogs(expected_events).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -324,7 +435,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::GetSeriesHistory(Some(expected_series_id)).into(), SonarrEvent::GetSeriesHistory(expected_series_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -350,7 +461,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::GetEpisodeHistory(Some(expected_episode_id)).into(), SonarrEvent::GetEpisodeHistory(expected_episode_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -368,5 +479,35 @@ mod tests {
assert!(result.is_ok()); assert!(result.is_ok());
} }
#[tokio::test]
async fn test_list_season_history_command() {
let expected_series_id = 1;
let expected_season_number = 1;
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(
SonarrEvent::GetSeasonHistory((expected_series_id, expected_season_number)).into(),
))
.times(1)
.returning(|_| {
Ok(Serdeable::Sonarr(SonarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::default()));
let list_season_history_command = SonarrListCommand::SeasonHistory {
series_id: 1,
season_number: 1,
};
let result =
SonarrListCommandHandler::with(&app_arc, list_season_history_command, &mut mock_network)
.handle()
.await;
assert!(result.is_ok());
}
} }
} }
@@ -75,7 +75,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrManualSearchCommand>
println!("Searching for episode releases. This may take a minute..."); println!("Searching for episode releases. This may take a minute...");
let resp = self let resp = self
.network .network
.handle_network_event(SonarrEvent::GetEpisodeReleases(Some(episode_id)).into()) .handle_network_event(SonarrEvent::GetEpisodeReleases(episode_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
@@ -86,9 +86,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrManualSearchCommand>
println!("Searching for season releases. This may take a minute..."); println!("Searching for season releases. This may take a minute...");
let resp = self let resp = self
.network .network
.handle_network_event( .handle_network_event(SonarrEvent::GetSeasonReleases((series_id, season_number)).into())
SonarrEvent::GetSeasonReleases(Some((series_id, season_number))).into(),
)
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
@@ -130,7 +130,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::GetEpisodeReleases(Some(expected_episode_id)).into(), SonarrEvent::GetEpisodeReleases(expected_episode_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -160,7 +160,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::GetSeasonReleases(Some((expected_series_id, expected_season_number))).into(), SonarrEvent::GetSeasonReleases((expected_series_id, expected_season_number)).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
+48 -3
View File
@@ -120,6 +120,32 @@ pub enum SonarrCommand {
}, },
#[command(about = "Test all Sonarr indexers")] #[command(about = "Test all Sonarr indexers")]
TestAllIndexers, TestAllIndexers,
#[command(about = "Toggle monitoring for the specified episode")]
ToggleEpisodeMonitoring {
#[arg(
long,
help = "The Sonarr ID of the episode to toggle monitoring on",
required = true
)]
episode_id: i64,
},
#[command(
about = "Toggle monitoring for the specified season that corresponds to the specified series ID"
)]
ToggleSeasonMonitoring {
#[arg(
long,
help = "The Sonarr ID of the series that the season belongs to",
required = true
)]
series_id: i64,
#[arg(
long,
help = "The season number to toggle monitoring for",
required = true
)]
season_number: i64,
},
} }
impl From<SonarrCommand> for Command { impl From<SonarrCommand> for Command {
@@ -219,21 +245,21 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrCommand> for SonarrCliHandler<'a, '
SonarrCommand::SearchNewSeries { query } => { SonarrCommand::SearchNewSeries { query } => {
let resp = self let resp = self
.network .network
.handle_network_event(SonarrEvent::SearchNewSeries(Some(query)).into()) .handle_network_event(SonarrEvent::SearchNewSeries(query).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
SonarrCommand::StartTask { task_name } => { SonarrCommand::StartTask { task_name } => {
let resp = self let resp = self
.network .network
.handle_network_event(SonarrEvent::StartTask(Some(task_name)).into()) .handle_network_event(SonarrEvent::StartTask(task_name).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
SonarrCommand::TestIndexer { indexer_id } => { SonarrCommand::TestIndexer { indexer_id } => {
let resp = self let resp = self
.network .network
.handle_network_event(SonarrEvent::TestIndexer(Some(indexer_id)).into()) .handle_network_event(SonarrEvent::TestIndexer(indexer_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
@@ -245,6 +271,25 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrCommand> for SonarrCliHandler<'a, '
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
SonarrCommand::ToggleEpisodeMonitoring { episode_id } => {
let resp = self
.network
.handle_network_event(SonarrEvent::ToggleEpisodeMonitoring(episode_id).into())
.await?;
serde_json::to_string_pretty(&resp)?
}
SonarrCommand::ToggleSeasonMonitoring {
series_id,
season_number,
} => {
let resp = self
.network
.handle_network_event(
SonarrEvent::ToggleSeasonMonitoring((series_id, season_number)).into(),
)
.await?;
serde_json::to_string_pretty(&resp)?
}
}; };
Ok(result) Ok(result)
+1 -1
View File
@@ -71,7 +71,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrRefreshCommand>
SonarrRefreshCommand::Series { series_id } => { SonarrRefreshCommand::Series { series_id } => {
let resp = self let resp = self
.network .network
.handle_network_event(SonarrEvent::UpdateAndScanSeries(Some(series_id)).into()) .handle_network_event(SonarrEvent::UpdateAndScanSeries(series_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
@@ -119,7 +119,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::UpdateAndScanSeries(Some(expected_series_id)).into(), SonarrEvent::UpdateAndScanSeries(expected_series_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
+145 -8
View File
@@ -142,6 +142,80 @@ mod tests {
assert!(result.is_ok()); assert!(result.is_ok());
} }
#[test]
fn test_toggle_episode_monitoring_requires_episode_id() {
let result =
Cli::command().try_get_matches_from(["managarr", "sonarr", "toggle-episode-monitoring"]);
assert!(result.is_err());
assert_eq!(
result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument
);
}
#[test]
fn test_toggle_episode_monitoring_requirements_satisfied() {
let result = Cli::command().try_get_matches_from([
"managarr",
"sonarr",
"toggle-episode-monitoring",
"--episode-id",
"1",
]);
assert!(result.is_ok());
}
#[test]
fn test_toggle_season_monitoring_requires_series_id() {
let result = Cli::command().try_get_matches_from([
"managarr",
"sonarr",
"toggle-season-monitoring",
"--season-number",
"1",
]);
assert!(result.is_err());
assert_eq!(
result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument
);
}
#[test]
fn test_toggle_season_monitoring_requires_season_number() {
let result = Cli::command().try_get_matches_from([
"managarr",
"sonarr",
"toggle-season-monitoring",
"--series-id",
"1",
]);
assert!(result.is_err());
assert_eq!(
result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument
);
}
#[test]
fn test_toggle_season_monitoring_requirements_satisfied() {
let result = Cli::command().try_get_matches_from([
"managarr",
"sonarr",
"toggle-season-monitoring",
"--series-id",
"1",
"--season-number",
"1",
]);
assert!(result.is_ok());
}
} }
mod handler { mod handler {
@@ -272,7 +346,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::DeleteBlocklistItem(Some(expected_blocklist_item_id)).into(), SonarrEvent::DeleteBlocklistItem(expected_blocklist_item_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -360,7 +434,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::EditAllIndexerSettings(Some(expected_edit_all_indexer_settings)).into(), SonarrEvent::EditAllIndexerSettings(expected_edit_all_indexer_settings).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -396,7 +470,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::GetEpisodeReleases(Some(expected_episode_id)).into(), SonarrEvent::GetEpisodeReleases(expected_episode_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -424,7 +498,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::TriggerAutomaticEpisodeSearch(Some(expected_episode_id)).into(), SonarrEvent::TriggerAutomaticEpisodeSearch(expected_episode_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -497,7 +571,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::UpdateAndScanSeries(Some(expected_series_id)).into(), SonarrEvent::UpdateAndScanSeries(expected_series_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -523,7 +597,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::SearchNewSeries(Some(expected_search_query)).into(), SonarrEvent::SearchNewSeries(expected_search_query).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -550,7 +624,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::StartTask(Some(expected_task_name)).into(), SonarrEvent::StartTask(expected_task_name).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -577,7 +651,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::TestIndexer(Some(expected_indexer_id)).into(), SonarrEvent::TestIndexer(expected_indexer_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -616,5 +690,68 @@ mod tests {
assert!(result.is_ok()); assert!(result.is_ok());
} }
#[tokio::test]
async fn test_list_toggle_episode_monitoring_command() {
let expected_episode_id = 1;
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(
SonarrEvent::ToggleEpisodeMonitoring(expected_episode_id).into(),
))
.times(1)
.returning(|_| {
Ok(Serdeable::Sonarr(SonarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::default()));
let toggle_episode_monitoring_command =
SonarrCommand::ToggleEpisodeMonitoring { episode_id: 1 };
let result = SonarrCliHandler::with(
&app_arc,
toggle_episode_monitoring_command,
&mut mock_network,
)
.handle()
.await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_list_toggle_season_monitoring_command() {
let expected_series_id = 1;
let expected_season_number = 1;
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(
SonarrEvent::ToggleSeasonMonitoring((expected_series_id, expected_season_number)).into(),
))
.times(1)
.returning(|_| {
Ok(Serdeable::Sonarr(SonarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::default()));
let toggle_season_monitoring_command = SonarrCommand::ToggleSeasonMonitoring {
series_id: 1,
season_number: 1,
};
let result = SonarrCliHandler::with(
&app_arc,
toggle_season_monitoring_command,
&mut mock_network,
)
.handle()
.await;
assert!(result.is_ok());
}
} }
} }
@@ -83,7 +83,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrTriggerAutomaticSearchCommand>
SonarrTriggerAutomaticSearchCommand::Series { series_id } => { SonarrTriggerAutomaticSearchCommand::Series { series_id } => {
let resp = self let resp = self
.network .network
.handle_network_event(SonarrEvent::TriggerAutomaticSeriesSearch(Some(series_id)).into()) .handle_network_event(SonarrEvent::TriggerAutomaticSeriesSearch(series_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
@@ -94,7 +94,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrTriggerAutomaticSearchCommand>
let resp = self let resp = self
.network .network
.handle_network_event( .handle_network_event(
SonarrEvent::TriggerAutomaticSeasonSearch(Some((series_id, season_number))).into(), SonarrEvent::TriggerAutomaticSeasonSearch((series_id, season_number)).into(),
) )
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
@@ -102,7 +102,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrTriggerAutomaticSearchCommand>
SonarrTriggerAutomaticSearchCommand::Episode { episode_id } => { SonarrTriggerAutomaticSearchCommand::Episode { episode_id } => {
let resp = self let resp = self
.network .network
.handle_network_event(SonarrEvent::TriggerAutomaticEpisodeSearch(Some(episode_id)).into()) .handle_network_event(SonarrEvent::TriggerAutomaticEpisodeSearch(episode_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
@@ -166,7 +166,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::TriggerAutomaticSeriesSearch(Some(expected_series_id)).into(), SonarrEvent::TriggerAutomaticSeriesSearch(expected_series_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -197,11 +197,8 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::TriggerAutomaticSeasonSearch(Some(( SonarrEvent::TriggerAutomaticSeasonSearch((expected_series_id, expected_season_number))
expected_series_id, .into(),
expected_season_number,
)))
.into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -233,7 +230,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::TriggerAutomaticEpisodeSearch(Some(expected_episode_id)).into(), SonarrEvent::TriggerAutomaticEpisodeSearch(expected_episode_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
+7
View File
@@ -19,6 +19,7 @@ pub enum Key {
Home, Home,
End, End,
Tab, Tab,
BackTab,
Delete, Delete,
Ctrl(char), Ctrl(char),
Char(char), Char(char),
@@ -40,6 +41,7 @@ impl Display for Key {
Key::Home => write!(f, "<home>"), Key::Home => write!(f, "<home>"),
Key::End => write!(f, "<end>"), Key::End => write!(f, "<end>"),
Key::Tab => write!(f, "<tab>"), Key::Tab => write!(f, "<tab>"),
Key::BackTab => write!(f, "<shift-tab>"),
Key::Delete => write!(f, "<del>"), Key::Delete => write!(f, "<del>"),
_ => write!(f, "<{self:?}>"), _ => write!(f, "<{self:?}>"),
} }
@@ -75,6 +77,11 @@ impl From<KeyEvent> for Key {
KeyEvent { KeyEvent {
code: KeyCode::End, .. code: KeyCode::End, ..
} => Key::End, } => Key::End,
KeyEvent {
code: KeyCode::BackTab,
modifiers: KeyModifiers::SHIFT,
..
} => Key::BackTab,
KeyEvent { KeyEvent {
code: KeyCode::Tab, .. code: KeyCode::Tab, ..
} => Key::Tab, } => Key::Tab,
+14
View File
@@ -17,6 +17,7 @@ mod tests {
#[case(Key::Home, "home")] #[case(Key::Home, "home")]
#[case(Key::End, "end")] #[case(Key::End, "end")]
#[case(Key::Tab, "tab")] #[case(Key::Tab, "tab")]
#[case(Key::BackTab, "shift-tab")]
#[case(Key::Delete, "del")] #[case(Key::Delete, "del")]
#[case(Key::Char('q'), "q")] #[case(Key::Char('q'), "q")]
#[case(Key::Ctrl('q'), "ctrl-q")] #[case(Key::Ctrl('q'), "ctrl-q")]
@@ -67,6 +68,19 @@ mod tests {
assert_eq!(Key::from(KeyEvent::from(KeyCode::Tab)), Key::Tab); assert_eq!(Key::from(KeyEvent::from(KeyCode::Tab)), Key::Tab);
} }
#[test]
fn test_key_from_back_tab() {
assert_eq!(
Key::from(KeyEvent {
code: KeyCode::BackTab,
modifiers: KeyModifiers::SHIFT,
kind: KeyEventKind::Press,
state: KeyEventState::NONE
}),
Key::BackTab
);
}
#[test] #[test]
fn test_key_from_delete() { fn test_key_from_delete() {
assert_eq!(Key::from(KeyEvent::from(KeyCode::Delete)), Key::Delete); assert_eq!(Key::from(KeyEvent::from(KeyCode::Delete)), Key::Delete);
+195 -68
View File
@@ -99,86 +99,96 @@ mod test_utils {
#[macro_export] #[macro_export]
macro_rules! test_iterable_scroll { macro_rules! test_iterable_scroll {
($func:ident, $handler:ident, $data_ref:ident, $block:expr, $context:expr) => { ($func:ident, $handler:ident, $servarr_data:ident, $data_ref:ident, $block:expr, $context:expr) => {
#[rstest] #[rstest]
fn $func(#[values(DEFAULT_KEYBINDINGS.up.key, DEFAULT_KEYBINDINGS.down.key)] key: Key) { fn $func(#[values(DEFAULT_KEYBINDINGS.up.key, DEFAULT_KEYBINDINGS.down.key)] key: Key) {
let mut app = App::default(); let mut app = App::default();
app.push_navigation_stack($block.into());
app app
.data .data
.radarr_data .$servarr_data
.$data_ref .$data_ref
.set_items(vec!["Test 1".to_owned(), "Test 2".to_owned()]); .set_items(vec!["Test 1".to_owned(), "Test 2".to_owned()]);
$handler::with(&key, &mut app, &$block, &$context).handle(); $handler::with(&key, &mut app, &$block, &$context).handle();
assert_str_eq!(app.data.radarr_data.$data_ref.current_selection(), "Test 2"); pretty_assertions::assert_str_eq!(
app.data.$servarr_data.$data_ref.current_selection(),
"Test 2"
);
$handler::with(&key, &mut app, &$block, &$context).handle(); $handler::with(&key, &mut app, &$block, &$context).handle();
assert_str_eq!(app.data.radarr_data.$data_ref.current_selection(), "Test 1"); pretty_assertions::assert_str_eq!(
app.data.$servarr_data.$data_ref.current_selection(),
"Test 1"
);
} }
}; };
($func:ident, $handler:ident, $data_ref:ident, $items:ident, $block:expr, $context:expr, $field:ident) => { ($func:ident, $handler:ident, $servarr_data:ident, $data_ref:ident, $items:ident, $block:expr, $context:expr, $field:ident) => {
#[rstest] #[rstest]
fn $func(#[values(DEFAULT_KEYBINDINGS.up.key, DEFAULT_KEYBINDINGS.down.key)] key: Key) { fn $func(#[values(DEFAULT_KEYBINDINGS.up.key, DEFAULT_KEYBINDINGS.down.key)] key: Key) {
let mut app = App::default(); let mut app = App::default();
app.push_navigation_stack($block.into());
app app
.data .data
.radarr_data .$servarr_data
.$data_ref .$data_ref
.set_items(simple_stateful_iterable_vec!($items)); .set_items(simple_stateful_iterable_vec!($items));
$handler::with(&key, &mut app, &$block, &$context).handle(); $handler::with(key, &mut app, $block, $context).handle();
assert_str_eq!( pretty_assertions::assert_str_eq!(
app.data.radarr_data.$data_ref.current_selection().$field, app.data.$servarr_data.$data_ref.current_selection().$field,
"Test 2" "Test 2"
); );
$handler::with(&key, &mut app, &$block, &$context).handle(); $handler::with(key, &mut app, $block, $context).handle();
assert_str_eq!( pretty_assertions::assert_str_eq!(
app.data.radarr_data.$data_ref.current_selection().$field, app.data.$servarr_data.$data_ref.current_selection().$field,
"Test 1" "Test 1"
); );
} }
}; };
($func:ident, $handler:ident, $data_ref:ident, $items:expr, $block:expr, $context:expr, $field:ident) => { ($func:ident, $handler:ident, $servarr_data:ident, $data_ref:ident, $items:expr, $block:expr, $context:expr, $field:ident) => {
#[rstest] #[rstest]
fn $func(#[values(DEFAULT_KEYBINDINGS.up.key, DEFAULT_KEYBINDINGS.down.key)] key: Key) { fn $func(#[values(DEFAULT_KEYBINDINGS.up.key, DEFAULT_KEYBINDINGS.down.key)] key: Key) {
let mut app = App::default(); let mut app = App::default();
app.data.radarr_data.$data_ref.set_items($items); app.push_navigation_stack($block.into());
app.data.$servarr_data.$data_ref.set_items($items);
$handler::with(&key, &mut app, &$block, &$context).handle(); $handler::with(key, &mut app, $block, $context).handle();
assert_str_eq!( pretty_assertions::assert_str_eq!(
app.data.radarr_data.$data_ref.current_selection().$field, app.data.$servarr_data.$data_ref.current_selection().$field,
"Test 2" "Test 2"
); );
$handler::with(&key, &mut app, &$block, &$context).handle(); $handler::with(key, &mut app, $block, $context).handle();
assert_str_eq!( pretty_assertions::assert_str_eq!(
app.data.radarr_data.$data_ref.current_selection().$field, app.data.$servarr_data.$data_ref.current_selection().$field,
"Test 1" "Test 1"
); );
} }
}; };
($func:ident, $handler:ident, $data_ref:ident, $items:expr, $block:expr, $context:expr, $field:ident, $conversion_fn:ident) => { ($func:ident, $handler:ident, $servarr_data:ident, $data_ref:ident, $items:expr, $block:expr, $context:expr, $field:ident, $conversion_fn:ident) => {
#[rstest] #[rstest]
fn $func(#[values(DEFAULT_KEYBINDINGS.up.key, DEFAULT_KEYBINDINGS.down.key)] key: Key) { fn $func(#[values(DEFAULT_KEYBINDINGS.up.key, DEFAULT_KEYBINDINGS.down.key)] key: Key) {
let mut app = App::default(); let mut app = App::default();
app.data.radarr_data.$data_ref.set_items($items); app.push_navigation_stack($block.into());
app.data.$servarr_data.$data_ref.set_items($items);
$handler::with(&key, &mut app, &$block, &$context).handle(); $handler::with(key, &mut app, $block, $context).handle();
assert_str_eq!( pretty_assertions::assert_str_eq!(
app app
.data .data
.radarr_data .$servarr_data
.$data_ref .$data_ref
.current_selection() .current_selection()
.$field .$field
@@ -186,12 +196,12 @@ mod test_utils {
"Test 2" "Test 2"
); );
$handler::with(&key, &mut app, &$block, &$context).handle(); $handler::with(key, &mut app, $block, $context).handle();
assert_str_eq!( pretty_assertions::assert_str_eq!(
app app
.data .data
.radarr_data .$servarr_data
.$data_ref .$data_ref
.current_selection() .current_selection()
.$field .$field
@@ -204,86 +214,96 @@ mod test_utils {
#[macro_export] #[macro_export]
macro_rules! test_iterable_home_and_end { macro_rules! test_iterable_home_and_end {
($func:ident, $handler:ident, $data_ref:ident, $block:expr, $context:expr) => { ($func:ident, $handler:ident, $servarr_data:ident, $data_ref:ident, $block:expr, $context:expr) => {
#[test] #[test]
fn $func() { fn $func() {
let mut app = App::default(); let mut app = App::default();
app.data.radarr_data.$data_ref.set_items(vec![ app.push_navigation_stack($block.into());
app.data.$servarr_data.$data_ref.set_items(vec![
"Test 1".to_owned(), "Test 1".to_owned(),
"Test 2".to_owned(), "Test 2".to_owned(),
"Test 3".to_owned(), "Test 3".to_owned(),
]); ]);
$handler::with(&DEFAULT_KEYBINDINGS.end.key, &mut app, &$block, &$context).handle(); $handler::with(DEFAULT_KEYBINDINGS.end.key, &mut app, $block, $context).handle();
assert_str_eq!(app.data.radarr_data.$data_ref.current_selection(), "Test 3"); pretty_assertions::assert_str_eq!(
app.data.$servarr_data.$data_ref.current_selection(),
"Test 3"
);
$handler::with(&DEFAULT_KEYBINDINGS.home.key, &mut app, &$block, &$context).handle(); $handler::with(DEFAULT_KEYBINDINGS.home.key, &mut app, $block, $context).handle();
assert_str_eq!(app.data.radarr_data.$data_ref.current_selection(), "Test 1"); pretty_assertions::assert_str_eq!(
app.data.$servarr_data.$data_ref.current_selection(),
"Test 1"
);
} }
}; };
($func:ident, $handler:ident, $data_ref:ident, $items:ident, $block:expr, $context:expr, $field:ident) => { ($func:ident, $handler:ident, $servarr_data:ident, $data_ref:ident, $items:ident, $block:expr, $context:expr, $field:ident) => {
#[test] #[test]
fn $func() { fn $func() {
let mut app = App::default(); let mut app = App::default();
app.push_navigation_stack($block.into());
app app
.data .data
.radarr_data .$servarr_data
.$data_ref .$data_ref
.set_items(extended_stateful_iterable_vec!($items)); .set_items(extended_stateful_iterable_vec!($items));
$handler::with(&DEFAULT_KEYBINDINGS.end.key, &mut app, &$block, &$context).handle(); $handler::with(DEFAULT_KEYBINDINGS.end.key, &mut app, $block, $context).handle();
assert_str_eq!( pretty_assertions::assert_str_eq!(
app.data.radarr_data.$data_ref.current_selection().$field, app.data.$servarr_data.$data_ref.current_selection().$field,
"Test 3" "Test 3"
); );
$handler::with(&DEFAULT_KEYBINDINGS.home.key, &mut app, &$block, &$context).handle(); $handler::with(DEFAULT_KEYBINDINGS.home.key, &mut app, $block, $context).handle();
assert_str_eq!( pretty_assertions::assert_str_eq!(
app.data.radarr_data.$data_ref.current_selection().$field, app.data.$servarr_data.$data_ref.current_selection().$field,
"Test 1" "Test 1"
); );
} }
}; };
($func:ident, $handler:ident, $data_ref:ident, $items:expr, $block:expr, $context:expr, $field:ident) => { ($func:ident, $handler:ident, $servarr_data:ident, $data_ref:ident, $items:expr, $block:expr, $context:expr, $field:ident) => {
#[test] #[test]
fn $func() { fn $func() {
let mut app = App::default(); let mut app = App::default();
app.data.radarr_data.$data_ref.set_items($items); app.push_navigation_stack($block.into());
app.data.$servarr_data.$data_ref.set_items($items);
$handler::with(&DEFAULT_KEYBINDINGS.end.key, &mut app, &$block, &$context).handle(); $handler::with(DEFAULT_KEYBINDINGS.end.key, &mut app, $block, $context).handle();
assert_str_eq!( pretty_assertions::assert_str_eq!(
app.data.radarr_data.$data_ref.current_selection().$field, app.data.$servarr_data.$data_ref.current_selection().$field,
"Test 3" "Test 3"
); );
$handler::with(&DEFAULT_KEYBINDINGS.home.key, &mut app, &$block, &$context).handle(); $handler::with(DEFAULT_KEYBINDINGS.home.key, &mut app, $block, $context).handle();
assert_str_eq!( pretty_assertions::assert_str_eq!(
app.data.radarr_data.$data_ref.current_selection().$field, app.data.$servarr_data.$data_ref.current_selection().$field,
"Test 1" "Test 1"
); );
} }
}; };
($func:ident, $handler:ident, $data_ref:ident, $items:expr, $block:expr, $context:expr, $field:ident, $conversion_fn:ident) => { ($func:ident, $handler:ident, $servarr_data:ident, $data_ref:ident, $items:expr, $block:expr, $context:expr, $field:ident, $conversion_fn:ident) => {
#[test] #[test]
fn $func() { fn $func() {
let mut app = App::default(); let mut app = App::default();
app.data.radarr_data.$data_ref.set_items($items); app.push_navigation_stack($block.into());
app.data.$servarr_data.$data_ref.set_items($items);
$handler::with(&DEFAULT_KEYBINDINGS.end.key, &mut app, &$block, &$context).handle(); $handler::with(DEFAULT_KEYBINDINGS.end.key, &mut app, $block, $context).handle();
assert_str_eq!( pretty_assertions::assert_str_eq!(
app app
.data .data
.radarr_data .$servarr_data
.$data_ref .$data_ref
.current_selection() .current_selection()
.$field .$field
@@ -291,12 +311,12 @@ mod test_utils {
"Test 3" "Test 3"
); );
$handler::with(&DEFAULT_KEYBINDINGS.home.key, &mut app, &$block, &$context).handle(); $handler::with(DEFAULT_KEYBINDINGS.home.key, &mut app, $block, $context).handle();
assert_str_eq!( pretty_assertions::assert_str_eq!(
app app
.data .data
.radarr_data .$servarr_data
.$data_ref .$data_ref
.current_selection() .current_selection()
.$field .$field
@@ -311,18 +331,125 @@ mod test_utils {
macro_rules! test_handler_delegation { macro_rules! test_handler_delegation {
($handler:ident, $base:expr, $active_block:expr) => { ($handler:ident, $base:expr, $active_block:expr) => {
let mut app = App::default(); let mut app = App::default();
app.push_navigation_stack($base.clone().into()); app.data.sonarr_data.history.set_items(vec![
app.push_navigation_stack($active_block.clone().into()); $crate::models::sonarr_models::SonarrHistoryItem::default(),
]);
app
.data
.sonarr_data
.root_folders
.set_items(vec![$crate::models::servarr_models::RootFolder::default()]);
app
.data
.sonarr_data
.indexers
.set_items(vec![$crate::models::servarr_models::Indexer::default()]);
app
.data
.sonarr_data
.blocklist
.set_items(vec![$crate::models::sonarr_models::BlocklistItem::default()]);
app.data.sonarr_data.add_searched_series =
Some($crate::models::stateful_table::StatefulTable::default());
app
.data
.radarr_data
.movies
.set_items(vec![$crate::models::radarr_models::Movie::default()]);
app
.data
.radarr_data
.collections
.set_items(vec![$crate::models::radarr_models::Collection::default()]);
app.data.radarr_data.collection_movies.set_items(vec![
$crate::models::radarr_models::CollectionMovie::default(),
]);
app
.data
.radarr_data
.indexers
.set_items(vec![$crate::models::servarr_models::Indexer::default()]);
app
.data
.radarr_data
.root_folders
.set_items(vec![$crate::models::servarr_models::RootFolder::default()]);
app
.data
.radarr_data
.blocklist
.set_items(vec![$crate::models::radarr_models::BlocklistItem::default()]);
app.data.radarr_data.add_searched_movies =
Some($crate::models::stateful_table::StatefulTable::default());
let mut movie_details_modal =
$crate::models::servarr_data::radarr::modals::MovieDetailsModal::default();
movie_details_modal.movie_history.set_items(vec![
$crate::models::radarr_models::MovieHistoryItem::default(),
]);
movie_details_modal
.movie_cast
.set_items(vec![$crate::models::radarr_models::Credit::default()]);
movie_details_modal
.movie_crew
.set_items(vec![$crate::models::radarr_models::Credit::default()]);
movie_details_modal
.movie_releases
.set_items(vec![$crate::models::radarr_models::RadarrRelease::default()]);
app.data.radarr_data.movie_details_modal = Some(movie_details_modal);
let mut season_details_modal =
$crate::models::servarr_data::sonarr::modals::SeasonDetailsModal::default();
season_details_modal.season_history.set_items(vec![
$crate::models::sonarr_models::SonarrHistoryItem::default(),
]);
season_details_modal.episode_details_modal =
Some($crate::models::servarr_data::sonarr::modals::EpisodeDetailsModal::default());
app.data.sonarr_data.season_details_modal = Some(season_details_modal);
let mut series_history = $crate::models::stateful_table::StatefulTable::default();
series_history.set_items(vec![
$crate::models::sonarr_models::SonarrHistoryItem::default(),
]);
app.data.sonarr_data.series_history = Some(series_history);
app
.data
.sonarr_data
.series
.set_items(vec![$crate::models::sonarr_models::Series::default()]);
app.push_navigation_stack($base.into());
app.push_navigation_stack($active_block.into());
$handler::with( $handler::with(DEFAULT_KEYBINDINGS.esc.key, &mut app, $active_block, None).handle();
&DEFAULT_KEYBINDINGS.esc.key,
&mut app,
&$active_block,
&None,
)
.handle();
assert_eq!(app.get_current_route(), &$base.into()); pretty_assertions::assert_eq!(app.get_current_route(), $base.into());
};
}
#[macro_export]
macro_rules! assert_delete_prompt {
($handler:ident, $block:expr, $expected_block:expr) => {
let mut app = App::default();
$handler::with(DELETE_KEY, &mut app, $block, None).handle();
pretty_assertions::assert_eq!(app.get_current_route(), $expected_block.into());
};
($handler:ident, $app:expr, $block:expr, $expected_block:expr) => {
$handler::with(DELETE_KEY, &mut $app, $block, None).handle();
pretty_assertions::assert_eq!($app.get_current_route(), $expected_block.into());
};
}
#[macro_export]
macro_rules! assert_refresh_key {
($handler:ident, $block:expr) => {
let mut app = App::default();
app.push_navigation_stack($block.into());
$handler::with(DEFAULT_KEYBINDINGS.refresh.key, &mut app, $block, None).handle();
pretty_assertions::assert_eq!(app.get_current_route(), $block.into());
assert!(app.should_refresh);
}; };
} }
} }
+86 -3
View File
@@ -1,10 +1,20 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::models::radarr_models::Movie;
use crate::models::sonarr_models::Series;
use pretty_assertions::assert_eq;
use rstest::rstest; use rstest::rstest;
use tokio_util::sync::CancellationToken;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::handle_events;
use crate::handlers::{handle_clear_errors, handle_prompt_toggle}; use crate::handlers::{handle_clear_errors, handle_prompt_toggle};
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
use crate::models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock;
use crate::models::HorizontallyScrollableText;
use crate::models::Route;
#[test] #[test]
fn test_handle_clear_errors() { fn test_handle_clear_errors() {
@@ -17,17 +27,90 @@ mod tests {
} }
#[rstest] #[rstest]
fn test_handle_prompt_toggle_left_right(#[values(Key::Left, Key::Right)] key: Key) { #[case(ActiveRadarrBlock::Movies.into(), ActiveRadarrBlock::SearchMovie.into())]
#[case(ActiveSonarrBlock::Series.into(), ActiveSonarrBlock::SearchSeries.into())]
fn test_handle_events(#[case] base_block: Route, #[case] top_block: Route) {
let mut app = App::default(); let mut app = App::default();
app.push_navigation_stack(base_block);
app.push_navigation_stack(top_block);
app
.data
.sonarr_data
.series
.set_items(vec![Series::default()]);
app
.data
.radarr_data
.movies
.set_items(vec![Movie::default()]);
handle_events(DEFAULT_KEYBINDINGS.esc.key, &mut app);
assert_eq!(app.get_current_route(), base_block);
}
#[rstest]
#[case(0, ActiveSonarrBlock::Series, ActiveSonarrBlock::Series)]
#[case(1, ActiveRadarrBlock::Movies, ActiveRadarrBlock::Movies)]
fn test_handle_change_tabs<T>(#[case] index: usize, #[case] left_block: T, #[case] right_block: T)
where
T: Into<Route> + Copy,
{
let mut app = App::default();
app.error = "Test".into();
app.server_tabs.set_index(index);
handle_events(DEFAULT_KEYBINDINGS.previous_servarr.key, &mut app);
assert_eq!(app.server_tabs.get_active_route(), left_block.into());
assert_eq!(app.get_current_route(), left_block.into());
assert!(app.is_first_render);
assert_eq!(app.error, HorizontallyScrollableText::default());
assert!(app.cancellation_token.is_cancelled());
app.server_tabs.set_index(index);
app.is_first_render = false;
app.error = "Test".into();
app.cancellation_token = CancellationToken::new();
handle_events(DEFAULT_KEYBINDINGS.next_servarr.key, &mut app);
assert_eq!(app.server_tabs.get_active_route(), right_block.into());
assert_eq!(app.get_current_route(), right_block.into());
assert!(app.is_first_render);
assert_eq!(app.error, HorizontallyScrollableText::default());
assert!(app.cancellation_token.is_cancelled());
}
#[rstest]
fn test_handle_prompt_toggle_left_right_radarr(#[values(Key::Left, Key::Right)] key: Key) {
let mut app = App::default();
app.push_navigation_stack(ActiveRadarrBlock::Movies.into());
assert!(!app.data.radarr_data.prompt_confirm); assert!(!app.data.radarr_data.prompt_confirm);
handle_prompt_toggle(&mut app, &key); handle_prompt_toggle(&mut app, key);
assert!(app.data.radarr_data.prompt_confirm); assert!(app.data.radarr_data.prompt_confirm);
handle_prompt_toggle(&mut app, &key); handle_prompt_toggle(&mut app, key);
assert!(!app.data.radarr_data.prompt_confirm); assert!(!app.data.radarr_data.prompt_confirm);
} }
#[rstest]
fn test_handle_prompt_toggle_left_right_sonarr(#[values(Key::Left, Key::Right)] key: Key) {
let mut app = App::default();
app.push_navigation_stack(ActiveSonarrBlock::Series.into());
assert!(!app.data.sonarr_data.prompt_confirm);
handle_prompt_toggle(&mut app, key);
assert!(app.data.sonarr_data.prompt_confirm);
handle_prompt_toggle(&mut app, key);
assert!(!app.data.sonarr_data.prompt_confirm);
}
} }
+62 -22
View File
@@ -1,4 +1,5 @@
use radarr_handlers::RadarrHandler; use radarr_handlers::RadarrHandler;
use sonarr_handlers::SonarrHandler;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
@@ -6,6 +7,7 @@ use crate::event::Key;
use crate::models::{HorizontallyScrollableText, Route}; use crate::models::{HorizontallyScrollableText, Route};
mod radarr_handlers; mod radarr_handlers;
mod sonarr_handlers;
#[cfg(test)] #[cfg(test)]
#[path = "handlers_tests.rs"] #[path = "handlers_tests.rs"]
@@ -14,45 +16,46 @@ mod handlers_tests;
#[cfg(test)] #[cfg(test)]
#[path = "handler_test_utils.rs"] #[path = "handler_test_utils.rs"]
pub mod handler_test_utils; pub mod handler_test_utils;
mod table_handler;
pub trait KeyEventHandler<'a, 'b, T: Into<Route>> { pub trait KeyEventHandler<'a, 'b, T: Into<Route> + Copy> {
fn handle_key_event(&mut self) { fn handle_key_event(&mut self) {
let key = self.get_key(); let key = self.get_key();
match key { match key {
_ if *key == DEFAULT_KEYBINDINGS.up.key => { _ if key == DEFAULT_KEYBINDINGS.up.key => {
if self.is_ready() { if self.is_ready() {
self.handle_scroll_up(); self.handle_scroll_up();
} }
} }
_ if *key == DEFAULT_KEYBINDINGS.down.key => { _ if key == DEFAULT_KEYBINDINGS.down.key => {
if self.is_ready() { if self.is_ready() {
self.handle_scroll_down(); self.handle_scroll_down();
} }
} }
_ if *key == DEFAULT_KEYBINDINGS.home.key => { _ if key == DEFAULT_KEYBINDINGS.home.key => {
if self.is_ready() { if self.is_ready() {
self.handle_home(); self.handle_home();
} }
} }
_ if *key == DEFAULT_KEYBINDINGS.end.key => { _ if key == DEFAULT_KEYBINDINGS.end.key => {
if self.is_ready() { if self.is_ready() {
self.handle_end(); self.handle_end();
} }
} }
_ if *key == DEFAULT_KEYBINDINGS.delete.key => { _ if key == DEFAULT_KEYBINDINGS.delete.key => {
if self.is_ready() { if self.is_ready() {
self.handle_delete(); self.handle_delete();
} }
} }
_ if *key == DEFAULT_KEYBINDINGS.left.key || *key == DEFAULT_KEYBINDINGS.right.key => { _ if key == DEFAULT_KEYBINDINGS.left.key || key == DEFAULT_KEYBINDINGS.right.key => {
self.handle_left_right_action() self.handle_left_right_action()
} }
_ if *key == DEFAULT_KEYBINDINGS.submit.key => { _ if key == DEFAULT_KEYBINDINGS.submit.key => {
if self.is_ready() { if self.is_ready() {
self.handle_submit(); self.handle_submit();
} }
} }
_ if *key == DEFAULT_KEYBINDINGS.esc.key => self.handle_esc(), _ if key == DEFAULT_KEYBINDINGS.esc.key => self.handle_esc(),
_ => { _ => {
if self.is_ready() { if self.is_ready() {
self.handle_char_key_event(); self.handle_char_key_event();
@@ -65,9 +68,9 @@ pub trait KeyEventHandler<'a, 'b, T: Into<Route>> {
self.handle_key_event(); self.handle_key_event();
} }
fn accepts(active_block: &'a T) -> bool; fn accepts(active_block: T) -> bool;
fn with(key: &'a Key, app: &'a mut App<'b>, active_block: &'a T, context: &'a Option<T>) -> Self; fn with(key: Key, app: &'a mut App<'b>, active_block: T, context: Option<T>) -> Self;
fn get_key(&self) -> &Key; fn get_key(&self) -> Key;
fn is_ready(&self) -> bool; fn is_ready(&self) -> bool;
fn handle_scroll_up(&mut self); fn handle_scroll_up(&mut self);
fn handle_scroll_down(&mut self); fn handle_scroll_down(&mut self);
@@ -81,8 +84,26 @@ pub trait KeyEventHandler<'a, 'b, T: Into<Route>> {
} }
pub fn handle_events(key: Key, app: &mut App<'_>) { pub fn handle_events(key: Key, app: &mut App<'_>) {
if let Route::Radarr(active_radarr_block, context) = *app.get_current_route() { if key == DEFAULT_KEYBINDINGS.next_servarr.key {
RadarrHandler::with(&key, app, &active_radarr_block, &context).handle() app.reset();
app.server_tabs.next();
app.pop_and_push_navigation_stack(app.server_tabs.get_active_route());
app.cancellation_token.cancel();
} else if key == DEFAULT_KEYBINDINGS.previous_servarr.key {
app.reset();
app.server_tabs.previous();
app.pop_and_push_navigation_stack(app.server_tabs.get_active_route());
app.cancellation_token.cancel();
} else {
match app.get_current_route() {
Route::Radarr(active_radarr_block, context) => {
RadarrHandler::with(key, app, active_radarr_block, context).handle()
}
Route::Sonarr(active_sonarr_block, context) => {
SonarrHandler::with(key, app, active_sonarr_block, context).handle()
}
_ => (),
}
} }
} }
@@ -92,11 +113,17 @@ fn handle_clear_errors(app: &mut App<'_>) {
} }
} }
fn handle_prompt_toggle(app: &mut App<'_>, key: &Key) { fn handle_prompt_toggle(app: &mut App<'_>, key: Key) {
match key { match key {
_ if *key == DEFAULT_KEYBINDINGS.left.key || *key == DEFAULT_KEYBINDINGS.right.key => { _ if key == DEFAULT_KEYBINDINGS.left.key || key == DEFAULT_KEYBINDINGS.right.key => {
if let Route::Radarr(_, _) = *app.get_current_route() { match app.get_current_route() {
app.data.radarr_data.prompt_confirm = !app.data.radarr_data.prompt_confirm; Route::Radarr(_, _) => {
app.data.radarr_data.prompt_confirm = !app.data.radarr_data.prompt_confirm
}
Route::Sonarr(_, _) => {
app.data.sonarr_data.prompt_confirm = !app.data.sonarr_data.prompt_confirm
}
_ => (),
} }
} }
_ => (), _ => (),
@@ -107,10 +134,10 @@ fn handle_prompt_toggle(app: &mut App<'_>, key: &Key) {
macro_rules! handle_text_box_left_right_keys { macro_rules! handle_text_box_left_right_keys {
($self:expr, $key:expr, $input:expr) => { ($self:expr, $key:expr, $input:expr) => {
match $self.key { match $self.key {
_ if *$key == DEFAULT_KEYBINDINGS.left.key => { _ if $key == $crate::app::key_binding::DEFAULT_KEYBINDINGS.left.key => {
$input.scroll_left(); $input.scroll_left();
} }
_ if *$key == DEFAULT_KEYBINDINGS.right.key => { _ if $key == $crate::app::key_binding::DEFAULT_KEYBINDINGS.right.key => {
$input.scroll_right(); $input.scroll_right();
} }
_ => (), _ => (),
@@ -122,13 +149,26 @@ macro_rules! handle_text_box_left_right_keys {
macro_rules! handle_text_box_keys { macro_rules! handle_text_box_keys {
($self:expr, $key:expr, $input:expr) => { ($self:expr, $key:expr, $input:expr) => {
match $self.key { match $self.key {
_ if *$key == DEFAULT_KEYBINDINGS.backspace.key => { _ if $key == $crate::app::key_binding::DEFAULT_KEYBINDINGS.backspace.key => {
$input.pop(); $input.pop();
} }
Key::Char(character) => { Key::Char(character) => {
$input.push(*character); $input.push(character);
} }
_ => (), _ => (),
} }
}; };
} }
#[macro_export]
macro_rules! handle_prompt_left_right_keys {
($self:expr, $confirm_prompt:expr, $data:ident) => {
if $self.app.data.$data.selected_block.get_active_block() == $confirm_prompt {
handle_prompt_toggle($self.app, $self.key);
} else if $self.key == $crate::app::key_binding::DEFAULT_KEYBINDINGS.left.key {
$self.app.data.$data.selected_block.left();
} else {
$self.app.data.$data.selected_block.right();
}
};
}
@@ -14,246 +14,6 @@ mod tests {
use crate::models::radarr_models::{BlocklistItem, BlocklistItemMovie}; use crate::models::radarr_models::{BlocklistItem, BlocklistItemMovie};
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, BLOCKLIST_BLOCKS}; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, BLOCKLIST_BLOCKS};
use crate::models::servarr_models::{Language, Quality, QualityWrapper}; use crate::models::servarr_models::{Language, Quality, QualityWrapper};
use crate::models::stateful_table::SortOption;
mod test_handle_scroll_up_and_down {
use pretty_assertions::{assert_eq, assert_str_eq};
use rstest::rstest;
use crate::models::radarr_models::BlocklistItem;
use crate::{simple_stateful_iterable_vec, test_iterable_scroll};
use super::*;
test_iterable_scroll!(
test_blocklist_scroll,
BlocklistHandler,
blocklist,
simple_stateful_iterable_vec!(BlocklistItem, String, source_title),
ActiveRadarrBlock::Blocklist,
None,
source_title,
to_string
);
#[rstest]
fn test_blocklist_scroll_no_op_when_not_ready(
#[values(DEFAULT_KEYBINDINGS.up.key, DEFAULT_KEYBINDINGS.down.key)] key: Key,
) {
let mut app = App::default();
app.is_loading = true;
app
.data
.radarr_data
.blocklist
.set_items(simple_stateful_iterable_vec!(
BlocklistItem,
String,
source_title
));
BlocklistHandler::with(&key, &mut app, &ActiveRadarrBlock::Blocklist, &None).handle();
assert_str_eq!(
app
.data
.radarr_data
.blocklist
.current_selection()
.source_title
.to_string(),
"Test 1"
);
BlocklistHandler::with(&key, &mut app, &ActiveRadarrBlock::Blocklist, &None).handle();
assert_str_eq!(
app
.data
.radarr_data
.blocklist
.current_selection()
.source_title
.to_string(),
"Test 1"
);
}
#[rstest]
fn test_blocklist_sort_scroll(
#[values(DEFAULT_KEYBINDINGS.up.key, DEFAULT_KEYBINDINGS.down.key)] key: Key,
) {
let blocklist_field_vec = sort_options();
let mut app = App::default();
app.data.radarr_data.blocklist.sorting(sort_options());
if key == Key::Up {
for i in (0..blocklist_field_vec.len()).rev() {
BlocklistHandler::with(
&key,
&mut app,
&ActiveRadarrBlock::BlocklistSortPrompt,
&None,
)
.handle();
assert_eq!(
app
.data
.radarr_data
.blocklist
.sort
.as_ref()
.unwrap()
.current_selection(),
&blocklist_field_vec[i]
);
}
} else {
for i in 0..blocklist_field_vec.len() {
BlocklistHandler::with(
&key,
&mut app,
&ActiveRadarrBlock::BlocklistSortPrompt,
&None,
)
.handle();
assert_eq!(
app
.data
.radarr_data
.blocklist
.sort
.as_ref()
.unwrap()
.current_selection(),
&blocklist_field_vec[(i + 1) % blocklist_field_vec.len()]
);
}
}
}
}
mod test_handle_home_end {
use pretty_assertions::{assert_eq, assert_str_eq};
use crate::models::radarr_models::BlocklistItem;
use crate::{extended_stateful_iterable_vec, test_iterable_home_and_end};
use super::*;
test_iterable_home_and_end!(
test_blocklist_home_and_end,
BlocklistHandler,
blocklist,
extended_stateful_iterable_vec!(BlocklistItem, String, source_title),
ActiveRadarrBlock::Blocklist,
None,
source_title,
to_string
);
#[test]
fn test_blocklist_home_and_end_no_op_when_not_ready() {
let mut app = App::default();
app.is_loading = true;
app
.data
.radarr_data
.blocklist
.set_items(extended_stateful_iterable_vec!(
BlocklistItem,
String,
source_title
));
BlocklistHandler::with(
&DEFAULT_KEYBINDINGS.end.key,
&mut app,
&ActiveRadarrBlock::Blocklist,
&None,
)
.handle();
assert_str_eq!(
app
.data
.radarr_data
.blocklist
.current_selection()
.source_title
.to_string(),
"Test 1"
);
BlocklistHandler::with(
&DEFAULT_KEYBINDINGS.home.key,
&mut app,
&ActiveRadarrBlock::Blocklist,
&None,
)
.handle();
assert_str_eq!(
app
.data
.radarr_data
.blocklist
.current_selection()
.source_title
.to_string(),
"Test 1"
);
}
#[test]
fn test_blocklist_sort_home_end() {
let blocklist_field_vec = sort_options();
let mut app = App::default();
app.data.radarr_data.blocklist.sorting(sort_options());
BlocklistHandler::with(
&DEFAULT_KEYBINDINGS.end.key,
&mut app,
&ActiveRadarrBlock::BlocklistSortPrompt,
&None,
)
.handle();
assert_eq!(
app
.data
.radarr_data
.blocklist
.sort
.as_ref()
.unwrap()
.current_selection(),
&blocklist_field_vec[blocklist_field_vec.len() - 1]
);
BlocklistHandler::with(
&DEFAULT_KEYBINDINGS.home.key,
&mut app,
&ActiveRadarrBlock::BlocklistSortPrompt,
&None,
)
.handle();
assert_eq!(
app
.data
.radarr_data
.blocklist
.sort
.as_ref()
.unwrap()
.current_selection(),
&blocklist_field_vec[0]
);
}
}
mod test_handle_delete { mod test_handle_delete {
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
@@ -267,11 +27,11 @@ mod tests {
let mut app = App::default(); let mut app = App::default();
app.data.radarr_data.blocklist.set_items(blocklist_vec()); app.data.radarr_data.blocklist.set_items(blocklist_vec());
BlocklistHandler::with(&DELETE_KEY, &mut app, &ActiveRadarrBlock::Blocklist, &None).handle(); BlocklistHandler::with(DELETE_KEY, &mut app, ActiveRadarrBlock::Blocklist, None).handle();
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
&ActiveRadarrBlock::DeleteBlocklistItemPrompt.into() ActiveRadarrBlock::DeleteBlocklistItemPrompt.into()
); );
} }
@@ -282,12 +42,9 @@ mod tests {
app.push_navigation_stack(ActiveRadarrBlock::Blocklist.into()); app.push_navigation_stack(ActiveRadarrBlock::Blocklist.into());
app.data.radarr_data.blocklist.set_items(blocklist_vec()); app.data.radarr_data.blocklist.set_items(blocklist_vec());
BlocklistHandler::with(&DELETE_KEY, &mut app, &ActiveRadarrBlock::Blocklist, &None).handle(); BlocklistHandler::with(DELETE_KEY, &mut app, ActiveRadarrBlock::Blocklist, None).handle();
assert_eq!( assert_eq!(app.get_current_route(), ActiveRadarrBlock::Blocklist.into());
app.get_current_route(),
&ActiveRadarrBlock::Blocklist.into()
);
} }
} }
@@ -304,21 +61,18 @@ mod tests {
app.data.radarr_data.main_tabs.set_index(3); app.data.radarr_data.main_tabs.set_index(3);
BlocklistHandler::with( BlocklistHandler::with(
&DEFAULT_KEYBINDINGS.left.key, DEFAULT_KEYBINDINGS.left.key,
&mut app, &mut app,
&ActiveRadarrBlock::Blocklist, ActiveRadarrBlock::Blocklist,
&None, None,
) )
.handle(); .handle();
assert_eq!( assert_eq!(
app.data.radarr_data.main_tabs.get_active_route(), app.data.radarr_data.main_tabs.get_active_route(),
&ActiveRadarrBlock::Downloads.into() ActiveRadarrBlock::Downloads.into()
);
assert_eq!(
app.get_current_route(),
&ActiveRadarrBlock::Downloads.into()
); );
assert_eq!(app.get_current_route(), ActiveRadarrBlock::Downloads.into());
} }
#[rstest] #[rstest]
@@ -328,20 +82,20 @@ mod tests {
app.data.radarr_data.main_tabs.set_index(3); app.data.radarr_data.main_tabs.set_index(3);
BlocklistHandler::with( BlocklistHandler::with(
&DEFAULT_KEYBINDINGS.right.key, DEFAULT_KEYBINDINGS.right.key,
&mut app, &mut app,
&ActiveRadarrBlock::Blocklist, ActiveRadarrBlock::Blocklist,
&None, None,
) )
.handle(); .handle();
assert_eq!( assert_eq!(
app.data.radarr_data.main_tabs.get_active_route(), app.data.radarr_data.main_tabs.get_active_route(),
&ActiveRadarrBlock::RootFolders.into() ActiveRadarrBlock::RootFolders.into()
); );
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
&ActiveRadarrBlock::RootFolders.into() ActiveRadarrBlock::RootFolders.into()
); );
} }
@@ -356,11 +110,11 @@ mod tests {
) { ) {
let mut app = App::default(); let mut app = App::default();
BlocklistHandler::with(&key, &mut app, &active_radarr_block, &None).handle(); BlocklistHandler::with(key, &mut app, active_radarr_block, None).handle();
assert!(app.data.radarr_data.prompt_confirm); assert!(app.data.radarr_data.prompt_confirm);
BlocklistHandler::with(&key, &mut app, &active_radarr_block, &None).handle(); BlocklistHandler::with(key, &mut app, active_radarr_block, None).handle();
assert!(!app.data.radarr_data.prompt_confirm); assert!(!app.data.radarr_data.prompt_confirm);
} }
@@ -382,11 +136,11 @@ mod tests {
app.data.radarr_data.blocklist.set_items(blocklist_vec()); app.data.radarr_data.blocklist.set_items(blocklist_vec());
app.push_navigation_stack(ActiveRadarrBlock::Blocklist.into()); app.push_navigation_stack(ActiveRadarrBlock::Blocklist.into());
BlocklistHandler::with(&SUBMIT_KEY, &mut app, &ActiveRadarrBlock::Blocklist, &None).handle(); BlocklistHandler::with(SUBMIT_KEY, &mut app, ActiveRadarrBlock::Blocklist, None).handle();
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
&ActiveRadarrBlock::BlocklistItemDetails.into() ActiveRadarrBlock::BlocklistItemDetails.into()
); );
} }
@@ -397,19 +151,16 @@ mod tests {
app.data.radarr_data.blocklist.set_items(blocklist_vec()); app.data.radarr_data.blocklist.set_items(blocklist_vec());
app.push_navigation_stack(ActiveRadarrBlock::Blocklist.into()); app.push_navigation_stack(ActiveRadarrBlock::Blocklist.into());
BlocklistHandler::with(&SUBMIT_KEY, &mut app, &ActiveRadarrBlock::Blocklist, &None).handle(); BlocklistHandler::with(SUBMIT_KEY, &mut app, ActiveRadarrBlock::Blocklist, None).handle();
assert_eq!( assert_eq!(app.get_current_route(), ActiveRadarrBlock::Blocklist.into());
app.get_current_route(),
&ActiveRadarrBlock::Blocklist.into()
);
} }
#[rstest] #[rstest]
#[case( #[case(
ActiveRadarrBlock::Blocklist, ActiveRadarrBlock::Blocklist,
ActiveRadarrBlock::DeleteBlocklistItemPrompt, ActiveRadarrBlock::DeleteBlocklistItemPrompt,
RadarrEvent::DeleteBlocklistItem(None) RadarrEvent::DeleteBlocklistItem(3)
)] )]
#[case( #[case(
ActiveRadarrBlock::Blocklist, ActiveRadarrBlock::Blocklist,
@@ -427,14 +178,14 @@ mod tests {
app.push_navigation_stack(base_route.into()); app.push_navigation_stack(base_route.into());
app.push_navigation_stack(prompt_block.into()); app.push_navigation_stack(prompt_block.into());
BlocklistHandler::with(&SUBMIT_KEY, &mut app, &prompt_block, &None).handle(); BlocklistHandler::with(SUBMIT_KEY, &mut app, prompt_block, None).handle();
assert!(app.data.radarr_data.prompt_confirm); assert!(app.data.radarr_data.prompt_confirm);
assert_eq!( assert_eq!(
app.data.radarr_data.prompt_confirm_action, app.data.radarr_data.prompt_confirm_action,
Some(expected_action) Some(expected_action)
); );
assert_eq!(app.get_current_route(), &base_route.into()); assert_eq!(app.get_current_route(), base_route.into());
} }
#[rstest] #[rstest]
@@ -450,42 +201,11 @@ mod tests {
app.push_navigation_stack(ActiveRadarrBlock::Blocklist.into()); app.push_navigation_stack(ActiveRadarrBlock::Blocklist.into());
app.push_navigation_stack(prompt_block.into()); app.push_navigation_stack(prompt_block.into());
BlocklistHandler::with(&SUBMIT_KEY, &mut app, &prompt_block, &None).handle(); BlocklistHandler::with(SUBMIT_KEY, &mut app, prompt_block, None).handle();
assert!(!app.data.radarr_data.prompt_confirm); assert!(!app.data.radarr_data.prompt_confirm);
assert_eq!(app.data.radarr_data.prompt_confirm_action, None); assert_eq!(app.data.radarr_data.prompt_confirm_action, None);
assert_eq!( assert_eq!(app.get_current_route(), ActiveRadarrBlock::Blocklist.into());
app.get_current_route(),
&ActiveRadarrBlock::Blocklist.into()
);
}
#[test]
fn test_blocklist_sort_prompt_submit() {
let mut app = App::default();
app.data.radarr_data.blocklist.sort_asc = true;
app.data.radarr_data.blocklist.sorting(sort_options());
app.data.radarr_data.blocklist.set_items(blocklist_vec());
app.push_navigation_stack(ActiveRadarrBlock::Blocklist.into());
app.push_navigation_stack(ActiveRadarrBlock::BlocklistSortPrompt.into());
let mut expected_vec = blocklist_vec();
expected_vec.sort_by(|a, b| a.id.cmp(&b.id));
expected_vec.reverse();
BlocklistHandler::with(
&SUBMIT_KEY,
&mut app,
&ActiveRadarrBlock::BlocklistSortPrompt,
&None,
)
.handle();
assert_eq!(
app.get_current_route(),
&ActiveRadarrBlock::Blocklist.into()
);
assert_eq!(app.data.radarr_data.blocklist.items, expected_vec);
} }
} }
@@ -517,9 +237,9 @@ mod tests {
app.push_navigation_stack(prompt_block.into()); app.push_navigation_stack(prompt_block.into());
app.data.radarr_data.prompt_confirm = true; app.data.radarr_data.prompt_confirm = true;
BlocklistHandler::with(&ESC_KEY, &mut app, &prompt_block, &None).handle(); BlocklistHandler::with(ESC_KEY, &mut app, prompt_block, None).handle();
assert_eq!(app.get_current_route(), &base_block.into()); assert_eq!(app.get_current_route(), base_block.into());
assert!(!app.data.radarr_data.prompt_confirm); assert!(!app.data.radarr_data.prompt_confirm);
} }
@@ -530,37 +250,14 @@ mod tests {
app.push_navigation_stack(ActiveRadarrBlock::BlocklistItemDetails.into()); app.push_navigation_stack(ActiveRadarrBlock::BlocklistItemDetails.into());
BlocklistHandler::with( BlocklistHandler::with(
&ESC_KEY, ESC_KEY,
&mut app, &mut app,
&ActiveRadarrBlock::BlocklistItemDetails, ActiveRadarrBlock::BlocklistItemDetails,
&None, None,
) )
.handle(); .handle();
assert_eq!( assert_eq!(app.get_current_route(), ActiveRadarrBlock::Blocklist.into());
app.get_current_route(),
&ActiveRadarrBlock::Blocklist.into()
);
}
#[test]
fn test_blocklist_sort_prompt_block_esc() {
let mut app = App::default();
app.push_navigation_stack(ActiveRadarrBlock::Blocklist.into());
app.push_navigation_stack(ActiveRadarrBlock::BlocklistSortPrompt.into());
BlocklistHandler::with(
&ESC_KEY,
&mut app,
&ActiveRadarrBlock::BlocklistSortPrompt,
&None,
)
.handle();
assert_eq!(
app.get_current_route(),
&ActiveRadarrBlock::Blocklist.into()
);
} }
#[rstest] #[rstest]
@@ -571,12 +268,9 @@ mod tests {
app.push_navigation_stack(ActiveRadarrBlock::Blocklist.into()); app.push_navigation_stack(ActiveRadarrBlock::Blocklist.into());
app.push_navigation_stack(ActiveRadarrBlock::Blocklist.into()); app.push_navigation_stack(ActiveRadarrBlock::Blocklist.into());
DownloadsHandler::with(&ESC_KEY, &mut app, &ActiveRadarrBlock::Blocklist, &None).handle(); DownloadsHandler::with(ESC_KEY, &mut app, ActiveRadarrBlock::Blocklist, None).handle();
assert_eq!( assert_eq!(app.get_current_route(), ActiveRadarrBlock::Blocklist.into());
app.get_current_route(),
&ActiveRadarrBlock::Blocklist.into()
);
assert!(app.error.text.is_empty()); assert!(app.error.text.is_empty());
} }
} }
@@ -596,17 +290,14 @@ mod tests {
app.push_navigation_stack(ActiveRadarrBlock::Blocklist.into()); app.push_navigation_stack(ActiveRadarrBlock::Blocklist.into());
BlocklistHandler::with( BlocklistHandler::with(
&DEFAULT_KEYBINDINGS.refresh.key, DEFAULT_KEYBINDINGS.refresh.key,
&mut app, &mut app,
&ActiveRadarrBlock::Blocklist, ActiveRadarrBlock::Blocklist,
&None, None,
) )
.handle(); .handle();
assert_eq!( assert_eq!(app.get_current_route(), ActiveRadarrBlock::Blocklist.into());
app.get_current_route(),
&ActiveRadarrBlock::Blocklist.into()
);
assert!(app.should_refresh); assert!(app.should_refresh);
} }
@@ -618,17 +309,14 @@ mod tests {
app.push_navigation_stack(ActiveRadarrBlock::Blocklist.into()); app.push_navigation_stack(ActiveRadarrBlock::Blocklist.into());
BlocklistHandler::with( BlocklistHandler::with(
&DEFAULT_KEYBINDINGS.refresh.key, DEFAULT_KEYBINDINGS.refresh.key,
&mut app, &mut app,
&ActiveRadarrBlock::Blocklist, ActiveRadarrBlock::Blocklist,
&None, None,
) )
.handle(); .handle();
assert_eq!( assert_eq!(app.get_current_route(), ActiveRadarrBlock::Blocklist.into());
app.get_current_route(),
&ActiveRadarrBlock::Blocklist.into()
);
assert!(!app.should_refresh); assert!(!app.should_refresh);
} }
@@ -638,16 +326,16 @@ mod tests {
app.data.radarr_data.blocklist.set_items(blocklist_vec()); app.data.radarr_data.blocklist.set_items(blocklist_vec());
BlocklistHandler::with( BlocklistHandler::with(
&DEFAULT_KEYBINDINGS.clear.key, DEFAULT_KEYBINDINGS.clear.key,
&mut app, &mut app,
&ActiveRadarrBlock::Blocklist, ActiveRadarrBlock::Blocklist,
&None, None,
) )
.handle(); .handle();
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
&ActiveRadarrBlock::BlocklistClearAllItemsPrompt.into() ActiveRadarrBlock::BlocklistClearAllItemsPrompt.into()
); );
} }
@@ -659,71 +347,21 @@ mod tests {
app.data.radarr_data.blocklist.set_items(blocklist_vec()); app.data.radarr_data.blocklist.set_items(blocklist_vec());
BlocklistHandler::with( BlocklistHandler::with(
&DEFAULT_KEYBINDINGS.clear.key, DEFAULT_KEYBINDINGS.clear.key,
&mut app, &mut app,
&ActiveRadarrBlock::Blocklist, ActiveRadarrBlock::Blocklist,
&None, None,
) )
.handle(); .handle();
assert_eq!( assert_eq!(app.get_current_route(), ActiveRadarrBlock::Blocklist.into());
app.get_current_route(),
&ActiveRadarrBlock::Blocklist.into()
);
}
#[test]
fn test_sort_key() {
let mut app = App::default();
app.data.radarr_data.blocklist.set_items(blocklist_vec());
BlocklistHandler::with(
&DEFAULT_KEYBINDINGS.sort.key,
&mut app,
&ActiveRadarrBlock::Blocklist,
&None,
)
.handle();
assert_eq!(
app.get_current_route(),
&ActiveRadarrBlock::BlocklistSortPrompt.into()
);
assert_eq!(
app.data.radarr_data.blocklist.sort.as_ref().unwrap().items,
blocklist_sorting_options()
);
assert!(!app.data.radarr_data.blocklist.sort_asc);
}
#[test]
fn test_sort_key_no_op_when_not_ready() {
let mut app = App::default();
app.is_loading = true;
app.push_navigation_stack(ActiveRadarrBlock::Blocklist.into());
app.data.radarr_data.blocklist.set_items(blocklist_vec());
BlocklistHandler::with(
&DEFAULT_KEYBINDINGS.sort.key,
&mut app,
&ActiveRadarrBlock::Blocklist,
&None,
)
.handle();
assert_eq!(
app.get_current_route(),
&ActiveRadarrBlock::Blocklist.into()
);
assert!(app.data.radarr_data.blocklist.sort.is_none());
assert!(!app.data.radarr_data.blocklist.sort_asc);
} }
#[rstest] #[rstest]
#[case( #[case(
ActiveRadarrBlock::Blocklist, ActiveRadarrBlock::Blocklist,
ActiveRadarrBlock::DeleteBlocklistItemPrompt, ActiveRadarrBlock::DeleteBlocklistItemPrompt,
RadarrEvent::DeleteBlocklistItem(None) RadarrEvent::DeleteBlocklistItem(3)
)] )]
#[case( #[case(
ActiveRadarrBlock::Blocklist, ActiveRadarrBlock::Blocklist,
@@ -741,10 +379,10 @@ mod tests {
app.push_navigation_stack(prompt_block.into()); app.push_navigation_stack(prompt_block.into());
BlocklistHandler::with( BlocklistHandler::with(
&DEFAULT_KEYBINDINGS.confirm.key, DEFAULT_KEYBINDINGS.confirm.key,
&mut app, &mut app,
&prompt_block, prompt_block,
&None, None,
) )
.handle(); .handle();
@@ -753,7 +391,7 @@ mod tests {
app.data.radarr_data.prompt_confirm_action, app.data.radarr_data.prompt_confirm_action,
Some(expected_action) Some(expected_action)
); );
assert_eq!(app.get_current_route(), &base_route.into()); assert_eq!(app.get_current_route(), base_route.into());
} }
} }
@@ -896,23 +534,39 @@ mod tests {
fn test_blocklist_handler_accepts() { fn test_blocklist_handler_accepts() {
ActiveRadarrBlock::iter().for_each(|active_radarr_block| { ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
if BLOCKLIST_BLOCKS.contains(&active_radarr_block) { if BLOCKLIST_BLOCKS.contains(&active_radarr_block) {
assert!(BlocklistHandler::accepts(&active_radarr_block)); assert!(BlocklistHandler::accepts(active_radarr_block));
} else { } else {
assert!(!BlocklistHandler::accepts(&active_radarr_block)); assert!(!BlocklistHandler::accepts(active_radarr_block));
} }
}) })
} }
#[test]
fn test_extract_blocklist_item_id() {
let mut app = App::default();
app.data.radarr_data.blocklist.set_items(blocklist_vec());
let blocklist_item_id = BlocklistHandler::with(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveRadarrBlock::Blocklist,
None,
)
.extract_blocklist_item_id();
assert_eq!(blocklist_item_id, 3);
}
#[test] #[test]
fn test_blocklist_handler_not_ready_when_loading() { fn test_blocklist_handler_not_ready_when_loading() {
let mut app = App::default(); let mut app = App::default();
app.is_loading = true; app.is_loading = true;
let handler = BlocklistHandler::with( let handler = BlocklistHandler::with(
&DEFAULT_KEYBINDINGS.esc.key, DEFAULT_KEYBINDINGS.esc.key,
&mut app, &mut app,
&ActiveRadarrBlock::Blocklist, ActiveRadarrBlock::Blocklist,
&None, None,
); );
assert!(!handler.is_ready()); assert!(!handler.is_ready());
@@ -924,10 +578,10 @@ mod tests {
app.is_loading = false; app.is_loading = false;
let handler = BlocklistHandler::with( let handler = BlocklistHandler::with(
&DEFAULT_KEYBINDINGS.esc.key, DEFAULT_KEYBINDINGS.esc.key,
&mut app, &mut app,
&ActiveRadarrBlock::Blocklist, ActiveRadarrBlock::Blocklist,
&None, None,
); );
assert!(!handler.is_ready()); assert!(!handler.is_ready());
@@ -944,10 +598,10 @@ mod tests {
.set_items(vec![BlocklistItem::default()]); .set_items(vec![BlocklistItem::default()]);
let handler = BlocklistHandler::with( let handler = BlocklistHandler::with(
&DEFAULT_KEYBINDINGS.esc.key, DEFAULT_KEYBINDINGS.esc.key,
&mut app, &mut app,
&ActiveRadarrBlock::Blocklist, ActiveRadarrBlock::Blocklist,
&None, None,
); );
assert!(handler.is_ready()); assert!(handler.is_ready());
@@ -1029,15 +683,4 @@ mod tests {
}, },
] ]
} }
fn sort_options() -> Vec<SortOption<BlocklistItem>> {
vec![SortOption {
name: "Test 1",
cmp_fn: Some(|a, b| {
b.source_title
.to_lowercase()
.cmp(&a.source_title.to_lowercase())
}),
}]
}
} }
+52 -103
View File
@@ -1,12 +1,13 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handle_table_events;
use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys; use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys;
use crate::handlers::table_handler::TableHandlingConfig;
use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler}; use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler};
use crate::models::radarr_models::BlocklistItem; use crate::models::radarr_models::BlocklistItem;
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, BLOCKLIST_BLOCKS}; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, BLOCKLIST_BLOCKS};
use crate::models::stateful_table::SortOption; use crate::models::stateful_table::SortOption;
use crate::models::Scrollable;
use crate::network::radarr_network::RadarrEvent; use crate::network::radarr_network::RadarrEvent;
#[cfg(test)] #[cfg(test)]
@@ -14,22 +15,47 @@ use crate::network::radarr_network::RadarrEvent;
mod blocklist_handler_tests; mod blocklist_handler_tests;
pub(super) struct BlocklistHandler<'a, 'b> { pub(super) struct BlocklistHandler<'a, 'b> {
key: &'a Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
active_radarr_block: &'a ActiveRadarrBlock, active_radarr_block: ActiveRadarrBlock,
_context: &'a Option<ActiveRadarrBlock>, _context: Option<ActiveRadarrBlock>,
}
impl<'a, 'b> BlocklistHandler<'a, 'b> {
handle_table_events!(
self,
blocklist,
self.app.data.radarr_data.blocklist,
BlocklistItem
);
fn extract_blocklist_item_id(&self) -> i64 {
self.app.data.radarr_data.blocklist.current_selection().id
}
} }
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for BlocklistHandler<'a, 'b> { impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for BlocklistHandler<'a, 'b> {
fn accepts(active_block: &'a ActiveRadarrBlock) -> bool { fn handle(&mut self) {
BLOCKLIST_BLOCKS.contains(active_block) let blocklist_table_handling_config =
TableHandlingConfig::new(ActiveRadarrBlock::Blocklist.into())
.sorting_block(ActiveRadarrBlock::BlocklistSortPrompt.into())
.sort_by_fn(|a: &BlocklistItem, b: &BlocklistItem| a.id.cmp(&b.id))
.sort_options(blocklist_sorting_options());
if !self.handle_blocklist_table_events(blocklist_table_handling_config) {
self.handle_key_event();
}
}
fn accepts(active_block: ActiveRadarrBlock) -> bool {
BLOCKLIST_BLOCKS.contains(&active_block)
} }
fn with( fn with(
key: &'a Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
active_block: &'a ActiveRadarrBlock, active_block: ActiveRadarrBlock,
context: &'a Option<ActiveRadarrBlock>, context: Option<ActiveRadarrBlock>,
) -> Self { ) -> Self {
BlocklistHandler { BlocklistHandler {
key, key,
@@ -39,7 +65,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for BlocklistHandler<'a,
} }
} }
fn get_key(&self) -> &Key { fn get_key(&self) -> Key {
self.key self.key
} }
@@ -47,72 +73,16 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for BlocklistHandler<'a,
!self.app.is_loading && !self.app.data.radarr_data.blocklist.is_empty() !self.app.is_loading && !self.app.data.radarr_data.blocklist.is_empty()
} }
fn handle_scroll_up(&mut self) { fn handle_scroll_up(&mut self) {}
match self.active_radarr_block {
ActiveRadarrBlock::Blocklist => self.app.data.radarr_data.blocklist.scroll_up(),
ActiveRadarrBlock::BlocklistSortPrompt => self
.app
.data
.radarr_data
.blocklist
.sort
.as_mut()
.unwrap()
.scroll_up(),
_ => (),
}
}
fn handle_scroll_down(&mut self) { fn handle_scroll_down(&mut self) {}
match self.active_radarr_block {
ActiveRadarrBlock::Blocklist => self.app.data.radarr_data.blocklist.scroll_down(),
ActiveRadarrBlock::BlocklistSortPrompt => self
.app
.data
.radarr_data
.blocklist
.sort
.as_mut()
.unwrap()
.scroll_down(),
_ => (),
}
}
fn handle_home(&mut self) { fn handle_home(&mut self) {}
match self.active_radarr_block {
ActiveRadarrBlock::Blocklist => self.app.data.radarr_data.blocklist.scroll_to_top(),
ActiveRadarrBlock::BlocklistSortPrompt => self
.app
.data
.radarr_data
.blocklist
.sort
.as_mut()
.unwrap()
.scroll_to_top(),
_ => (),
}
}
fn handle_end(&mut self) { fn handle_end(&mut self) {}
match self.active_radarr_block {
ActiveRadarrBlock::Blocklist => self.app.data.radarr_data.blocklist.scroll_to_bottom(),
ActiveRadarrBlock::BlocklistSortPrompt => self
.app
.data
.radarr_data
.blocklist
.sort
.as_mut()
.unwrap()
.scroll_to_bottom(),
_ => (),
}
}
fn handle_delete(&mut self) { fn handle_delete(&mut self) {
if self.active_radarr_block == &ActiveRadarrBlock::Blocklist { if self.active_radarr_block == ActiveRadarrBlock::Blocklist {
self self
.app .app
.push_navigation_stack(ActiveRadarrBlock::DeleteBlocklistItemPrompt.into()); .push_navigation_stack(ActiveRadarrBlock::DeleteBlocklistItemPrompt.into());
@@ -132,8 +102,9 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for BlocklistHandler<'a,
match self.active_radarr_block { match self.active_radarr_block {
ActiveRadarrBlock::DeleteBlocklistItemPrompt => { ActiveRadarrBlock::DeleteBlocklistItemPrompt => {
if self.app.data.radarr_data.prompt_confirm { if self.app.data.radarr_data.prompt_confirm {
self.app.data.radarr_data.prompt_confirm_action = self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DeleteBlocklistItem(
Some(RadarrEvent::DeleteBlocklistItem(None)); self.extract_blocklist_item_id(),
));
} }
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
@@ -145,18 +116,6 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for BlocklistHandler<'a,
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
} }
ActiveRadarrBlock::BlocklistSortPrompt => {
self
.app
.data
.radarr_data
.blocklist
.items
.sort_by(|a, b| a.id.cmp(&b.id));
self.app.data.radarr_data.blocklist.apply_sorting();
self.app.pop_navigation_stack();
}
ActiveRadarrBlock::Blocklist => { ActiveRadarrBlock::Blocklist => {
self self
.app .app
@@ -173,7 +132,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for BlocklistHandler<'a,
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
self.app.data.radarr_data.prompt_confirm = false; self.app.data.radarr_data.prompt_confirm = false;
} }
ActiveRadarrBlock::BlocklistItemDetails | ActiveRadarrBlock::BlocklistSortPrompt => { ActiveRadarrBlock::BlocklistItemDetails => {
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
} }
_ => handle_clear_errors(self.app), _ => handle_clear_errors(self.app),
@@ -184,38 +143,28 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for BlocklistHandler<'a,
let key = self.key; let key = self.key;
match self.active_radarr_block { match self.active_radarr_block {
ActiveRadarrBlock::Blocklist => match self.key { ActiveRadarrBlock::Blocklist => match self.key {
_ if *key == DEFAULT_KEYBINDINGS.refresh.key => { _ if key == DEFAULT_KEYBINDINGS.refresh.key => {
self.app.should_refresh = true; self.app.should_refresh = true;
} }
_ if *key == DEFAULT_KEYBINDINGS.clear.key => { _ if key == DEFAULT_KEYBINDINGS.clear.key => {
self self
.app .app
.push_navigation_stack(ActiveRadarrBlock::BlocklistClearAllItemsPrompt.into()); .push_navigation_stack(ActiveRadarrBlock::BlocklistClearAllItemsPrompt.into());
} }
_ if *key == DEFAULT_KEYBINDINGS.sort.key => {
self
.app
.data
.radarr_data
.blocklist
.sorting(blocklist_sorting_options());
self
.app
.push_navigation_stack(ActiveRadarrBlock::BlocklistSortPrompt.into());
}
_ => (), _ => (),
}, },
ActiveRadarrBlock::DeleteBlocklistItemPrompt => { ActiveRadarrBlock::DeleteBlocklistItemPrompt => {
if *key == DEFAULT_KEYBINDINGS.confirm.key { if key == DEFAULT_KEYBINDINGS.confirm.key {
self.app.data.radarr_data.prompt_confirm = true; self.app.data.radarr_data.prompt_confirm = true;
self.app.data.radarr_data.prompt_confirm_action = self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DeleteBlocklistItem(
Some(RadarrEvent::DeleteBlocklistItem(None)); self.extract_blocklist_item_id(),
));
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
} }
} }
ActiveRadarrBlock::BlocklistClearAllItemsPrompt => { ActiveRadarrBlock::BlocklistClearAllItemsPrompt => {
if *key == DEFAULT_KEYBINDINGS.confirm.key { if key == DEFAULT_KEYBINDINGS.confirm.key {
self.app.data.radarr_data.prompt_confirm = true; self.app.data.radarr_data.prompt_confirm = true;
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::ClearBlocklist); self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::ClearBlocklist);
@@ -1,35 +1,56 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handle_table_events;
use crate::handlers::table_handler::TableHandlingConfig;
use crate::handlers::KeyEventHandler; use crate::handlers::KeyEventHandler;
use crate::models::radarr_models::CollectionMovie;
use crate::models::servarr_data::radarr::radarr_data::{ use crate::models::servarr_data::radarr::radarr_data::{
ActiveRadarrBlock, ADD_MOVIE_SELECTION_BLOCKS, COLLECTION_DETAILS_BLOCKS, ActiveRadarrBlock, ADD_MOVIE_SELECTION_BLOCKS, COLLECTION_DETAILS_BLOCKS,
EDIT_COLLECTION_SELECTION_BLOCKS, EDIT_COLLECTION_SELECTION_BLOCKS,
}; };
use crate::models::stateful_table::StatefulTable; use crate::models::stateful_table::StatefulTable;
use crate::models::{BlockSelectionState, Scrollable}; use crate::models::BlockSelectionState;
#[cfg(test)] #[cfg(test)]
#[path = "collection_details_handler_tests.rs"] #[path = "collection_details_handler_tests.rs"]
mod collection_details_handler_tests; mod collection_details_handler_tests;
pub(super) struct CollectionDetailsHandler<'a, 'b> { pub(super) struct CollectionDetailsHandler<'a, 'b> {
key: &'a Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
active_radarr_block: &'a ActiveRadarrBlock, active_radarr_block: ActiveRadarrBlock,
_context: &'a Option<ActiveRadarrBlock>, _context: Option<ActiveRadarrBlock>,
}
impl<'a, 'b> CollectionDetailsHandler<'a, 'b> {
handle_table_events!(
self,
collection_movies,
self.app.data.radarr_data.collection_movies,
CollectionMovie
);
} }
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionDetailsHandler<'a, 'b> { impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionDetailsHandler<'a, 'b> {
fn accepts(active_block: &'a ActiveRadarrBlock) -> bool { fn handle(&mut self) {
COLLECTION_DETAILS_BLOCKS.contains(active_block) let collection_movies_table_handling_config =
TableHandlingConfig::new(ActiveRadarrBlock::CollectionDetails.into());
if !self.handle_collection_movies_table_events(collection_movies_table_handling_config) {
self.handle_key_event();
}
}
fn accepts(active_block: ActiveRadarrBlock) -> bool {
COLLECTION_DETAILS_BLOCKS.contains(&active_block)
} }
fn with( fn with(
key: &'a Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
active_block: &'a ActiveRadarrBlock, active_block: ActiveRadarrBlock,
_context: &'a Option<ActiveRadarrBlock>, _context: Option<ActiveRadarrBlock>,
) -> CollectionDetailsHandler<'a, 'b> { ) -> CollectionDetailsHandler<'a, 'b> {
CollectionDetailsHandler { CollectionDetailsHandler {
key, key,
@@ -39,7 +60,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionDetailsHan
} }
} }
fn get_key(&self) -> &Key { fn get_key(&self) -> Key {
self.key self.key
} }
@@ -47,41 +68,20 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionDetailsHan
!self.app.is_loading && !self.app.data.radarr_data.collection_movies.is_empty() !self.app.is_loading && !self.app.data.radarr_data.collection_movies.is_empty()
} }
fn handle_scroll_up(&mut self) { fn handle_scroll_up(&mut self) {}
if ActiveRadarrBlock::CollectionDetails == *self.active_radarr_block {
self.app.data.radarr_data.collection_movies.scroll_up()
}
}
fn handle_scroll_down(&mut self) { fn handle_scroll_down(&mut self) {}
if ActiveRadarrBlock::CollectionDetails == *self.active_radarr_block {
self.app.data.radarr_data.collection_movies.scroll_down()
}
}
fn handle_home(&mut self) { fn handle_home(&mut self) {}
if ActiveRadarrBlock::CollectionDetails == *self.active_radarr_block {
self.app.data.radarr_data.collection_movies.scroll_to_top();
}
}
fn handle_end(&mut self) { fn handle_end(&mut self) {}
if ActiveRadarrBlock::CollectionDetails == *self.active_radarr_block {
self
.app
.data
.radarr_data
.collection_movies
.scroll_to_bottom();
}
}
fn handle_delete(&mut self) {} fn handle_delete(&mut self) {}
fn handle_left_right_action(&mut self) {} fn handle_left_right_action(&mut self) {}
fn handle_submit(&mut self) { fn handle_submit(&mut self) {
if ActiveRadarrBlock::CollectionDetails == *self.active_radarr_block { if ActiveRadarrBlock::CollectionDetails == self.active_radarr_block {
let tmdb_id = self let tmdb_id = self
.app .app
.data .data
@@ -111,7 +111,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionDetailsHan
.into(), .into(),
); );
self.app.data.radarr_data.selected_block = self.app.data.radarr_data.selected_block =
BlockSelectionState::new(&ADD_MOVIE_SELECTION_BLOCKS); BlockSelectionState::new(ADD_MOVIE_SELECTION_BLOCKS);
self.app.data.radarr_data.add_movie_modal = Some((&self.app.data.radarr_data).into()); self.app.data.radarr_data.add_movie_modal = Some((&self.app.data.radarr_data).into());
} }
} }
@@ -129,19 +129,19 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionDetailsHan
} }
fn handle_char_key_event(&mut self) { fn handle_char_key_event(&mut self) {
if *self.active_radarr_block == ActiveRadarrBlock::CollectionDetails if self.active_radarr_block == ActiveRadarrBlock::CollectionDetails
&& *self.key == DEFAULT_KEYBINDINGS.edit.key && self.key == DEFAULT_KEYBINDINGS.edit.key
{ {
self.app.push_navigation_stack( self.app.push_navigation_stack(
( (
ActiveRadarrBlock::EditCollectionPrompt, ActiveRadarrBlock::EditCollectionPrompt,
Some(*self.active_radarr_block), Some(self.active_radarr_block),
) )
.into(), .into(),
); );
self.app.data.radarr_data.edit_collection_modal = Some((&self.app.data.radarr_data).into()); self.app.data.radarr_data.edit_collection_modal = Some((&self.app.data.radarr_data).into());
self.app.data.radarr_data.selected_block = self.app.data.radarr_data.selected_block =
BlockSelectionState::new(&EDIT_COLLECTION_SELECTION_BLOCKS); BlockSelectionState::new(EDIT_COLLECTION_SELECTION_BLOCKS);
} }
} }
} }
@@ -12,142 +12,6 @@ mod tests {
use crate::models::servarr_data::radarr::radarr_data::{ use crate::models::servarr_data::radarr::radarr_data::{
ActiveRadarrBlock, COLLECTION_DETAILS_BLOCKS, ActiveRadarrBlock, COLLECTION_DETAILS_BLOCKS,
}; };
use crate::models::HorizontallyScrollableText;
mod test_handle_scroll_up_and_down {
use rstest::rstest;
use crate::{simple_stateful_iterable_vec, test_iterable_scroll};
use super::*;
test_iterable_scroll!(
test_collection_details_scroll,
CollectionDetailsHandler,
collection_movies,
simple_stateful_iterable_vec!(CollectionMovie, HorizontallyScrollableText),
ActiveRadarrBlock::CollectionDetails,
None,
title,
to_string
);
#[rstest]
fn test_collection_details_scroll_no_op_when_not_ready(
#[values(
DEFAULT_KEYBINDINGS.up.key, DEFAULT_KEYBINDINGS.down.key
)]
key: Key,
) {
let mut app = App::default();
app.is_loading = true;
app
.data
.radarr_data
.collection_movies
.set_items(simple_stateful_iterable_vec!(
CollectionMovie,
HorizontallyScrollableText
));
CollectionDetailsHandler::with(&key, &mut app, &ActiveRadarrBlock::CollectionDetails, &None)
.handle();
assert_str_eq!(
app
.data
.radarr_data
.collection_movies
.current_selection()
.title
.to_string(),
"Test 1"
);
CollectionDetailsHandler::with(&key, &mut app, &ActiveRadarrBlock::CollectionDetails, &None)
.handle();
assert_str_eq!(
app
.data
.radarr_data
.collection_movies
.current_selection()
.title
.to_string(),
"Test 1"
);
}
}
mod test_handle_home_end {
use crate::{extended_stateful_iterable_vec, test_iterable_home_and_end};
use super::*;
test_iterable_home_and_end!(
test_collection_details_home_end,
CollectionDetailsHandler,
collection_movies,
extended_stateful_iterable_vec!(CollectionMovie, HorizontallyScrollableText),
ActiveRadarrBlock::CollectionDetails,
None,
title,
to_string
);
#[test]
fn test_collection_details_home_end_no_op_when_not_ready() {
let mut app = App::default();
app.is_loading = true;
app
.data
.radarr_data
.collection_movies
.set_items(extended_stateful_iterable_vec!(
CollectionMovie,
HorizontallyScrollableText
));
CollectionDetailsHandler::with(
&DEFAULT_KEYBINDINGS.end.key,
&mut app,
&ActiveRadarrBlock::CollectionDetails,
&None,
)
.handle();
assert_str_eq!(
app
.data
.radarr_data
.collection_movies
.current_selection()
.title
.to_string(),
"Test 1"
);
CollectionDetailsHandler::with(
&DEFAULT_KEYBINDINGS.home.key,
&mut app,
&ActiveRadarrBlock::CollectionDetails,
&None,
)
.handle();
assert_str_eq!(
app
.data
.radarr_data
.collection_movies
.current_selection()
.title
.to_string(),
"Test 1"
);
}
}
mod test_handle_submit { mod test_handle_submit {
use bimap::BiMap; use bimap::BiMap;
@@ -171,24 +35,24 @@ mod tests {
.set_items(vec![CollectionMovie::default()]); .set_items(vec![CollectionMovie::default()]);
app.data.radarr_data.quality_profile_map = app.data.radarr_data.quality_profile_map =
BiMap::from_iter([(1, "B - Test 2".to_owned()), (0, "A - Test 1".to_owned())]); BiMap::from_iter([(1, "B - Test 2".to_owned()), (0, "A - Test 1".to_owned())]);
app.data.radarr_data.selected_block = BlockSelectionState::new(&ADD_MOVIE_SELECTION_BLOCKS); app.data.radarr_data.selected_block = BlockSelectionState::new(ADD_MOVIE_SELECTION_BLOCKS);
app app
.data .data
.radarr_data .radarr_data
.selected_block .selected_block
.set_index(ADD_MOVIE_SELECTION_BLOCKS.len() - 1); .set_index(0, ADD_MOVIE_SELECTION_BLOCKS.len() - 1);
CollectionDetailsHandler::with( CollectionDetailsHandler::with(
&SUBMIT_KEY, SUBMIT_KEY,
&mut app, &mut app,
&ActiveRadarrBlock::CollectionDetails, ActiveRadarrBlock::CollectionDetails,
&None, None,
) )
.handle(); .handle();
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
&( (
ActiveRadarrBlock::AddMoviePrompt, ActiveRadarrBlock::AddMoviePrompt,
Some(ActiveRadarrBlock::CollectionDetails) Some(ActiveRadarrBlock::CollectionDetails)
) )
@@ -205,7 +69,7 @@ mod tests {
.is_empty()); .is_empty());
assert_eq!( assert_eq!(
app.data.radarr_data.selected_block.get_active_block(), app.data.radarr_data.selected_block.get_active_block(),
&ActiveRadarrBlock::AddMovieSelectRootFolder ActiveRadarrBlock::AddMovieSelectRootFolder
); );
assert!(!app assert!(!app
.data .data
@@ -250,16 +114,16 @@ mod tests {
.set_items(vec![CollectionMovie::default()]); .set_items(vec![CollectionMovie::default()]);
CollectionDetailsHandler::with( CollectionDetailsHandler::with(
&SUBMIT_KEY, SUBMIT_KEY,
&mut app, &mut app,
&ActiveRadarrBlock::CollectionDetails, ActiveRadarrBlock::CollectionDetails,
&None, None,
) )
.handle(); .handle();
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
&ActiveRadarrBlock::CollectionDetails.into() ActiveRadarrBlock::CollectionDetails.into()
); );
assert!(app.data.radarr_data.add_movie_modal.is_none()); assert!(app.data.radarr_data.add_movie_modal.is_none());
} }
@@ -279,16 +143,16 @@ mod tests {
.set_items(vec![Movie::default()]); .set_items(vec![Movie::default()]);
CollectionDetailsHandler::with( CollectionDetailsHandler::with(
&SUBMIT_KEY, SUBMIT_KEY,
&mut app, &mut app,
&ActiveRadarrBlock::CollectionDetails, ActiveRadarrBlock::CollectionDetails,
&None, None,
) )
.handle(); .handle();
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
&ActiveRadarrBlock::ViewMovieOverview.into() ActiveRadarrBlock::ViewMovieOverview.into()
); );
} }
} }
@@ -313,16 +177,16 @@ mod tests {
.set_items(vec![CollectionMovie::default()]); .set_items(vec![CollectionMovie::default()]);
CollectionDetailsHandler::with( CollectionDetailsHandler::with(
&ESC_KEY, ESC_KEY,
&mut app, &mut app,
&ActiveRadarrBlock::CollectionDetails, ActiveRadarrBlock::CollectionDetails,
&None, None,
) )
.handle(); .handle();
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
&ActiveRadarrBlock::Collections.into() ActiveRadarrBlock::Collections.into()
); );
assert!(app.data.radarr_data.collection_movies.items.is_empty()); assert!(app.data.radarr_data.collection_movies.items.is_empty());
} }
@@ -334,16 +198,16 @@ mod tests {
app.push_navigation_stack(ActiveRadarrBlock::ViewMovieOverview.into()); app.push_navigation_stack(ActiveRadarrBlock::ViewMovieOverview.into());
CollectionDetailsHandler::with( CollectionDetailsHandler::with(
&ESC_KEY, ESC_KEY,
&mut app, &mut app,
&ActiveRadarrBlock::ViewMovieOverview, ActiveRadarrBlock::ViewMovieOverview,
&None, None,
) )
.handle(); .handle();
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
&ActiveRadarrBlock::CollectionDetails.into() ActiveRadarrBlock::CollectionDetails.into()
); );
} }
} }
@@ -367,7 +231,7 @@ mod tests {
test_edit_collection_key!( test_edit_collection_key!(
CollectionDetailsHandler, CollectionDetailsHandler,
ActiveRadarrBlock::CollectionDetails, ActiveRadarrBlock::CollectionDetails,
ActiveRadarrBlock::CollectionDetails Some(ActiveRadarrBlock::CollectionDetails)
); );
} }
@@ -388,16 +252,16 @@ mod tests {
app.data.radarr_data = radarr_data; app.data.radarr_data = radarr_data;
CollectionDetailsHandler::with( CollectionDetailsHandler::with(
&DEFAULT_KEYBINDINGS.edit.key, DEFAULT_KEYBINDINGS.edit.key,
&mut app, &mut app,
&ActiveRadarrBlock::CollectionDetails, ActiveRadarrBlock::CollectionDetails,
&None, None,
) )
.handle(); .handle();
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
&ActiveRadarrBlock::CollectionDetails.into() ActiveRadarrBlock::CollectionDetails.into()
); );
assert!(app.data.radarr_data.edit_collection_modal.is_none()); assert!(app.data.radarr_data.edit_collection_modal.is_none());
} }
@@ -407,9 +271,9 @@ mod tests {
fn test_collection_details_handler_accepts() { fn test_collection_details_handler_accepts() {
ActiveRadarrBlock::iter().for_each(|active_radarr_block| { ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
if COLLECTION_DETAILS_BLOCKS.contains(&active_radarr_block) { if COLLECTION_DETAILS_BLOCKS.contains(&active_radarr_block) {
assert!(CollectionDetailsHandler::accepts(&active_radarr_block)); assert!(CollectionDetailsHandler::accepts(active_radarr_block));
} else { } else {
assert!(!CollectionDetailsHandler::accepts(&active_radarr_block)); assert!(!CollectionDetailsHandler::accepts(active_radarr_block));
} }
}); });
} }
@@ -420,10 +284,10 @@ mod tests {
app.is_loading = true; app.is_loading = true;
let handler = CollectionDetailsHandler::with( let handler = CollectionDetailsHandler::with(
&DEFAULT_KEYBINDINGS.esc.key, DEFAULT_KEYBINDINGS.esc.key,
&mut app, &mut app,
&ActiveRadarrBlock::CollectionDetails, ActiveRadarrBlock::CollectionDetails,
&None, None,
); );
assert!(!handler.is_ready()); assert!(!handler.is_ready());
@@ -435,10 +299,10 @@ mod tests {
app.is_loading = false; app.is_loading = false;
let handler = CollectionDetailsHandler::with( let handler = CollectionDetailsHandler::with(
&DEFAULT_KEYBINDINGS.esc.key, DEFAULT_KEYBINDINGS.esc.key,
&mut app, &mut app,
&ActiveRadarrBlock::CollectionDetails, ActiveRadarrBlock::CollectionDetails,
&None, None,
); );
assert!(!handler.is_ready()); assert!(!handler.is_ready());
@@ -455,10 +319,10 @@ mod tests {
.set_items(vec![CollectionMovie::default()]); .set_items(vec![CollectionMovie::default()]);
let handler = CollectionDetailsHandler::with( let handler = CollectionDetailsHandler::with(
&DEFAULT_KEYBINDINGS.esc.key, DEFAULT_KEYBINDINGS.esc.key,
&mut app, &mut app,
&ActiveRadarrBlock::CollectionDetails, ActiveRadarrBlock::CollectionDetails,
&None, None,
); );
assert!(handler.is_ready()); assert!(handler.is_ready());
File diff suppressed because it is too large Load Diff
@@ -2,6 +2,8 @@ use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
use crate::models::radarr_models::EditCollectionParams;
use crate::models::servarr_data::radarr::modals::EditCollectionModal;
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, EDIT_COLLECTION_BLOCKS}; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, EDIT_COLLECTION_BLOCKS};
use crate::models::Scrollable; use crate::models::Scrollable;
use crate::network::radarr_network::RadarrEvent; use crate::network::radarr_network::RadarrEvent;
@@ -12,22 +14,67 @@ use crate::{handle_text_box_keys, handle_text_box_left_right_keys};
mod edit_collection_handler_tests; mod edit_collection_handler_tests;
pub(super) struct EditCollectionHandler<'a, 'b> { pub(super) struct EditCollectionHandler<'a, 'b> {
key: &'a Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
active_radarr_block: &'a ActiveRadarrBlock, active_radarr_block: ActiveRadarrBlock,
context: &'a Option<ActiveRadarrBlock>, context: Option<ActiveRadarrBlock>,
}
impl<'a, 'b> EditCollectionHandler<'a, 'b> {
fn build_edit_collection_params(&mut self) -> EditCollectionParams {
let collection_id = self.app.data.radarr_data.collections.current_selection().id;
let EditCollectionModal {
path,
search_on_add,
minimum_availability_list,
monitored,
quality_profile_list,
} = self
.app
.data
.radarr_data
.edit_collection_modal
.as_ref()
.unwrap();
let quality_profile = quality_profile_list.current_selection();
let quality_profile_id = *self
.app
.data
.radarr_data
.quality_profile_map
.iter()
.filter(|(_, value)| *value == quality_profile)
.map(|(key, _)| key)
.next()
.unwrap();
let root_folder_path: String = path.text.clone();
let monitored = monitored.unwrap_or_default();
let search_on_add = search_on_add.unwrap_or_default();
let minimum_availability = *minimum_availability_list.current_selection();
self.app.data.radarr_data.edit_collection_modal = None;
EditCollectionParams {
collection_id,
monitored: Some(monitored),
minimum_availability: Some(minimum_availability),
quality_profile_id: Some(quality_profile_id),
root_folder_path: Some(root_folder_path),
search_on_add: Some(search_on_add),
}
}
} }
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditCollectionHandler<'a, 'b> { impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditCollectionHandler<'a, 'b> {
fn accepts(active_block: &'a ActiveRadarrBlock) -> bool { fn accepts(active_block: ActiveRadarrBlock) -> bool {
EDIT_COLLECTION_BLOCKS.contains(active_block) EDIT_COLLECTION_BLOCKS.contains(&active_block)
} }
fn with( fn with(
key: &'a Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
active_block: &'a ActiveRadarrBlock, active_block: ActiveRadarrBlock,
context: &'a Option<ActiveRadarrBlock>, context: Option<ActiveRadarrBlock>,
) -> EditCollectionHandler<'a, 'b> { ) -> EditCollectionHandler<'a, 'b> {
EditCollectionHandler { EditCollectionHandler {
key, key,
@@ -37,7 +84,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditCollectionHandle
} }
} }
fn get_key(&self) -> &Key { fn get_key(&self) -> Key {
self.key self.key
} }
@@ -65,9 +112,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditCollectionHandle
.unwrap() .unwrap()
.quality_profile_list .quality_profile_list
.scroll_up(), .scroll_up(),
ActiveRadarrBlock::EditCollectionPrompt => { ActiveRadarrBlock::EditCollectionPrompt => self.app.data.radarr_data.selected_block.up(),
self.app.data.radarr_data.selected_block.previous()
}
_ => (), _ => (),
} }
} }
@@ -92,7 +137,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditCollectionHandle
.unwrap() .unwrap()
.quality_profile_list .quality_profile_list
.scroll_down(), .scroll_down(),
ActiveRadarrBlock::EditCollectionPrompt => self.app.data.radarr_data.selected_block.next(), ActiveRadarrBlock::EditCollectionPrompt => self.app.data.radarr_data.selected_block.down(),
_ => (), _ => (),
} }
} }
@@ -192,8 +237,9 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditCollectionHandle
match self.app.data.radarr_data.selected_block.get_active_block() { match self.app.data.radarr_data.selected_block.get_active_block() {
ActiveRadarrBlock::EditCollectionConfirmPrompt => { ActiveRadarrBlock::EditCollectionConfirmPrompt => {
if self.app.data.radarr_data.prompt_confirm { if self.app.data.radarr_data.prompt_confirm {
self.app.data.radarr_data.prompt_confirm_action = self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::EditCollection(
Some(RadarrEvent::EditCollection(None)); self.build_edit_collection_params(),
));
self.app.should_refresh = true; self.app.should_refresh = true;
} }
@@ -203,8 +249,8 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditCollectionHandle
| ActiveRadarrBlock::EditCollectionSelectQualityProfile => { | ActiveRadarrBlock::EditCollectionSelectQualityProfile => {
self.app.push_navigation_stack( self.app.push_navigation_stack(
( (
*self.app.data.radarr_data.selected_block.get_active_block(), self.app.data.radarr_data.selected_block.get_active_block(),
*self.context, self.context,
) )
.into(), .into(),
) )
@@ -212,8 +258,8 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditCollectionHandle
ActiveRadarrBlock::EditCollectionRootFolderPathInput => { ActiveRadarrBlock::EditCollectionRootFolderPathInput => {
self.app.push_navigation_stack( self.app.push_navigation_stack(
( (
*self.app.data.radarr_data.selected_block.get_active_block(), self.app.data.radarr_data.selected_block.get_active_block(),
*self.context, self.context,
) )
.into(), .into(),
); );
@@ -308,11 +354,13 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditCollectionHandle
} }
ActiveRadarrBlock::EditCollectionPrompt => { ActiveRadarrBlock::EditCollectionPrompt => {
if self.app.data.radarr_data.selected_block.get_active_block() if self.app.data.radarr_data.selected_block.get_active_block()
== &ActiveRadarrBlock::EditCollectionConfirmPrompt == ActiveRadarrBlock::EditCollectionConfirmPrompt
&& *key == DEFAULT_KEYBINDINGS.confirm.key && key == DEFAULT_KEYBINDINGS.confirm.key
{ {
self.app.data.radarr_data.prompt_confirm = true; self.app.data.radarr_data.prompt_confirm = true;
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::EditCollection(None)); self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::EditCollection(
self.build_edit_collection_params(),
));
self.app.should_refresh = true; self.app.should_refresh = true;
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
@@ -1,5 +1,6 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use bimap::BiMap;
use pretty_assertions::assert_str_eq; use pretty_assertions::assert_str_eq;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
@@ -7,8 +8,9 @@ mod tests {
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::radarr_handlers::collections::edit_collection_handler::EditCollectionHandler; use crate::handlers::radarr_handlers::collections::edit_collection_handler::EditCollectionHandler;
use crate::handlers::radarr_handlers::radarr_handler_test_utils::utils::collection;
use crate::handlers::KeyEventHandler; use crate::handlers::KeyEventHandler;
use crate::models::radarr_models::MinimumAvailability; use crate::models::radarr_models::{Collection, EditCollectionParams, MinimumAvailability};
use crate::models::servarr_data::radarr::modals::EditCollectionModal; use crate::models::servarr_data::radarr::modals::EditCollectionModal;
use crate::models::servarr_data::radarr::radarr_data::{ use crate::models::servarr_data::radarr::radarr_data::{
ActiveRadarrBlock, EDIT_COLLECTION_BLOCKS, ActiveRadarrBlock, EDIT_COLLECTION_BLOCKS,
@@ -44,10 +46,10 @@ mod tests {
if key == Key::Up { if key == Key::Up {
for i in (0..minimum_availability_vec.len()).rev() { for i in (0..minimum_availability_vec.len()).rev() {
EditCollectionHandler::with( EditCollectionHandler::with(
&key, key,
&mut app, &mut app,
&ActiveRadarrBlock::EditCollectionSelectMinimumAvailability, ActiveRadarrBlock::EditCollectionSelectMinimumAvailability,
&None, None,
) )
.handle(); .handle();
@@ -66,10 +68,10 @@ mod tests {
} else { } else {
for i in 0..minimum_availability_vec.len() { for i in 0..minimum_availability_vec.len() {
EditCollectionHandler::with( EditCollectionHandler::with(
&key, key,
&mut app, &mut app,
&ActiveRadarrBlock::EditCollectionSelectMinimumAvailability, ActiveRadarrBlock::EditCollectionSelectMinimumAvailability,
&None, None,
) )
.handle(); .handle();
@@ -104,10 +106,10 @@ mod tests {
.set_items(vec!["Test 1".to_owned(), "Test 2".to_owned()]); .set_items(vec!["Test 1".to_owned(), "Test 2".to_owned()]);
EditCollectionHandler::with( EditCollectionHandler::with(
&key, key,
&mut app, &mut app,
&ActiveRadarrBlock::EditCollectionSelectQualityProfile, ActiveRadarrBlock::EditCollectionSelectQualityProfile,
&None, None,
) )
.handle(); .handle();
@@ -124,10 +126,10 @@ mod tests {
); );
EditCollectionHandler::with( EditCollectionHandler::with(
&key, key,
&mut app, &mut app,
&ActiveRadarrBlock::EditCollectionSelectQualityProfile, ActiveRadarrBlock::EditCollectionSelectQualityProfile,
&None, None,
) )
.handle(); .handle();
@@ -149,26 +151,21 @@ mod tests {
let mut app = App::default(); let mut app = App::default();
app.data.radarr_data.edit_collection_modal = Some(EditCollectionModal::default()); app.data.radarr_data.edit_collection_modal = Some(EditCollectionModal::default());
app.data.radarr_data.selected_block = app.data.radarr_data.selected_block =
BlockSelectionState::new(&EDIT_COLLECTION_SELECTION_BLOCKS); BlockSelectionState::new(EDIT_COLLECTION_SELECTION_BLOCKS);
app.data.radarr_data.selected_block.next(); app.data.radarr_data.selected_block.down();
EditCollectionHandler::with( EditCollectionHandler::with(key, &mut app, ActiveRadarrBlock::EditCollectionPrompt, None)
&key, .handle();
&mut app,
&ActiveRadarrBlock::EditCollectionPrompt,
&None,
)
.handle();
if key == Key::Up { if key == Key::Up {
assert_eq!( assert_eq!(
app.data.radarr_data.selected_block.get_active_block(), app.data.radarr_data.selected_block.get_active_block(),
&ActiveRadarrBlock::EditCollectionToggleMonitored ActiveRadarrBlock::EditCollectionToggleMonitored
); );
} else { } else {
assert_eq!( assert_eq!(
app.data.radarr_data.selected_block.get_active_block(), app.data.radarr_data.selected_block.get_active_block(),
&ActiveRadarrBlock::EditCollectionSelectQualityProfile ActiveRadarrBlock::EditCollectionSelectQualityProfile
); );
} }
} }
@@ -181,20 +178,15 @@ mod tests {
app.is_loading = true; app.is_loading = true;
app.data.radarr_data.edit_collection_modal = Some(EditCollectionModal::default()); app.data.radarr_data.edit_collection_modal = Some(EditCollectionModal::default());
app.data.radarr_data.selected_block = app.data.radarr_data.selected_block =
BlockSelectionState::new(&EDIT_COLLECTION_SELECTION_BLOCKS); BlockSelectionState::new(EDIT_COLLECTION_SELECTION_BLOCKS);
app.data.radarr_data.selected_block.next(); app.data.radarr_data.selected_block.down();
EditCollectionHandler::with( EditCollectionHandler::with(key, &mut app, ActiveRadarrBlock::EditCollectionPrompt, None)
&key, .handle();
&mut app,
&ActiveRadarrBlock::EditCollectionPrompt,
&None,
)
.handle();
assert_eq!( assert_eq!(
app.data.radarr_data.selected_block.get_active_block(), app.data.radarr_data.selected_block.get_active_block(),
&ActiveRadarrBlock::EditCollectionSelectMinimumAvailability ActiveRadarrBlock::EditCollectionSelectMinimumAvailability
); );
} }
} }
@@ -224,10 +216,10 @@ mod tests {
.set_items(minimum_availability_vec.clone()); .set_items(minimum_availability_vec.clone());
EditCollectionHandler::with( EditCollectionHandler::with(
&DEFAULT_KEYBINDINGS.end.key, DEFAULT_KEYBINDINGS.end.key,
&mut app, &mut app,
&ActiveRadarrBlock::EditCollectionSelectMinimumAvailability, ActiveRadarrBlock::EditCollectionSelectMinimumAvailability,
&None, None,
) )
.handle(); .handle();
@@ -244,10 +236,10 @@ mod tests {
); );
EditCollectionHandler::with( EditCollectionHandler::with(
&DEFAULT_KEYBINDINGS.home.key, DEFAULT_KEYBINDINGS.home.key,
&mut app, &mut app,
&ActiveRadarrBlock::EditCollectionSelectMinimumAvailability, ActiveRadarrBlock::EditCollectionSelectMinimumAvailability,
&None, None,
) )
.handle(); .handle();
@@ -282,10 +274,10 @@ mod tests {
]); ]);
EditCollectionHandler::with( EditCollectionHandler::with(
&DEFAULT_KEYBINDINGS.end.key, DEFAULT_KEYBINDINGS.end.key,
&mut app, &mut app,
&ActiveRadarrBlock::EditCollectionSelectQualityProfile, ActiveRadarrBlock::EditCollectionSelectQualityProfile,
&None, None,
) )
.handle(); .handle();
@@ -302,10 +294,10 @@ mod tests {
); );
EditCollectionHandler::with( EditCollectionHandler::with(
&DEFAULT_KEYBINDINGS.home.key, DEFAULT_KEYBINDINGS.home.key,
&mut app, &mut app,
&ActiveRadarrBlock::EditCollectionSelectQualityProfile, ActiveRadarrBlock::EditCollectionSelectQualityProfile,
&None, None,
) )
.handle(); .handle();
@@ -331,10 +323,10 @@ mod tests {
}); });
EditCollectionHandler::with( EditCollectionHandler::with(
&DEFAULT_KEYBINDINGS.home.key, DEFAULT_KEYBINDINGS.home.key,
&mut app, &mut app,
&ActiveRadarrBlock::EditCollectionRootFolderPathInput, ActiveRadarrBlock::EditCollectionRootFolderPathInput,
&None, None,
) )
.handle(); .handle();
@@ -352,10 +344,10 @@ mod tests {
); );
EditCollectionHandler::with( EditCollectionHandler::with(
&DEFAULT_KEYBINDINGS.end.key, DEFAULT_KEYBINDINGS.end.key,
&mut app, &mut app,
&ActiveRadarrBlock::EditCollectionRootFolderPathInput, ActiveRadarrBlock::EditCollectionRootFolderPathInput,
&None, None,
) )
.handle(); .handle();
@@ -386,23 +378,13 @@ mod tests {
fn test_left_right_prompt_toggle(#[values(Key::Left, Key::Right)] key: Key) { fn test_left_right_prompt_toggle(#[values(Key::Left, Key::Right)] key: Key) {
let mut app = App::default(); let mut app = App::default();
EditCollectionHandler::with( EditCollectionHandler::with(key, &mut app, ActiveRadarrBlock::EditCollectionPrompt, None)
&key, .handle();
&mut app,
&ActiveRadarrBlock::EditCollectionPrompt,
&None,
)
.handle();
assert!(app.data.radarr_data.prompt_confirm); assert!(app.data.radarr_data.prompt_confirm);
EditCollectionHandler::with( EditCollectionHandler::with(key, &mut app, ActiveRadarrBlock::EditCollectionPrompt, None)
&key, .handle();
&mut app,
&ActiveRadarrBlock::EditCollectionPrompt,
&None,
)
.handle();
assert!(!app.data.radarr_data.prompt_confirm); assert!(!app.data.radarr_data.prompt_confirm);
} }
@@ -416,10 +398,10 @@ mod tests {
}); });
EditCollectionHandler::with( EditCollectionHandler::with(
&DEFAULT_KEYBINDINGS.left.key, DEFAULT_KEYBINDINGS.left.key,
&mut app, &mut app,
&ActiveRadarrBlock::EditCollectionRootFolderPathInput, ActiveRadarrBlock::EditCollectionRootFolderPathInput,
&None, None,
) )
.handle(); .handle();
@@ -437,10 +419,10 @@ mod tests {
); );
EditCollectionHandler::with( EditCollectionHandler::with(
&DEFAULT_KEYBINDINGS.right.key, DEFAULT_KEYBINDINGS.right.key,
&mut app, &mut app,
&ActiveRadarrBlock::EditCollectionRootFolderPathInput, ActiveRadarrBlock::EditCollectionRootFolderPathInput,
&None, None,
) )
.handle(); .handle();
@@ -484,10 +466,10 @@ mod tests {
app.push_navigation_stack(ActiveRadarrBlock::EditCollectionRootFolderPathInput.into()); app.push_navigation_stack(ActiveRadarrBlock::EditCollectionRootFolderPathInput.into());
EditCollectionHandler::with( EditCollectionHandler::with(
&SUBMIT_KEY, SUBMIT_KEY,
&mut app, &mut app,
&ActiveRadarrBlock::EditCollectionRootFolderPathInput, ActiveRadarrBlock::EditCollectionRootFolderPathInput,
&None, None,
) )
.handle(); .handle();
@@ -503,7 +485,7 @@ mod tests {
.is_empty()); .is_empty());
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
&ActiveRadarrBlock::EditCollectionPrompt.into() ActiveRadarrBlock::EditCollectionPrompt.into()
); );
} }
@@ -514,24 +496,24 @@ mod tests {
app.push_navigation_stack(ActiveRadarrBlock::Collections.into()); app.push_navigation_stack(ActiveRadarrBlock::Collections.into());
app.push_navigation_stack(ActiveRadarrBlock::EditCollectionPrompt.into()); app.push_navigation_stack(ActiveRadarrBlock::EditCollectionPrompt.into());
app.data.radarr_data.selected_block = app.data.radarr_data.selected_block =
BlockSelectionState::new(&EDIT_COLLECTION_SELECTION_BLOCKS); BlockSelectionState::new(EDIT_COLLECTION_SELECTION_BLOCKS);
app app
.data .data
.radarr_data .radarr_data
.selected_block .selected_block
.set_index(EDIT_COLLECTION_SELECTION_BLOCKS.len() - 1); .set_index(0, EDIT_COLLECTION_SELECTION_BLOCKS.len() - 1);
EditCollectionHandler::with( EditCollectionHandler::with(
&SUBMIT_KEY, SUBMIT_KEY,
&mut app, &mut app,
&ActiveRadarrBlock::EditCollectionPrompt, ActiveRadarrBlock::EditCollectionPrompt,
&None, None,
) )
.handle(); .handle();
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
&ActiveRadarrBlock::Collections.into() ActiveRadarrBlock::Collections.into()
); );
assert_eq!(app.data.radarr_data.prompt_confirm_action, None); assert_eq!(app.data.radarr_data.prompt_confirm_action, None);
} }
@@ -539,35 +521,63 @@ mod tests {
#[test] #[test]
fn test_edit_collection_confirm_prompt_prompt_confirmation_submit() { fn test_edit_collection_confirm_prompt_prompt_confirmation_submit() {
let mut app = App::default(); let mut app = App::default();
app.data.radarr_data.edit_collection_modal = Some(EditCollectionModal::default()); let mut edit_collection_modal = EditCollectionModal {
path: "/nfs/Test Path".into(),
monitored: Some(false),
search_on_add: Some(false),
..EditCollectionModal::default()
};
edit_collection_modal
.quality_profile_list
.set_items(vec!["Any".to_owned(), "HD - 1080p".to_owned()]);
edit_collection_modal
.minimum_availability_list
.set_items(Vec::from_iter(MinimumAvailability::iter()));
app.data.radarr_data.edit_collection_modal = Some(edit_collection_modal);
app.data.radarr_data.collections.set_items(vec![Collection {
monitored: false,
search_on_add: false,
..collection()
}]);
app.data.radarr_data.quality_profile_map =
BiMap::from_iter([(1111, "Any".to_owned()), (2222, "HD - 1080p".to_owned())]);
let expected_edit_collection_params = EditCollectionParams {
collection_id: 123,
monitored: Some(false),
minimum_availability: Some(MinimumAvailability::Announced),
quality_profile_id: Some(1111),
root_folder_path: Some("/nfs/Test Path".to_owned()),
search_on_add: Some(false),
};
app.push_navigation_stack(ActiveRadarrBlock::Collections.into()); app.push_navigation_stack(ActiveRadarrBlock::Collections.into());
app.push_navigation_stack(ActiveRadarrBlock::EditCollectionPrompt.into()); app.push_navigation_stack(ActiveRadarrBlock::EditCollectionPrompt.into());
app.data.radarr_data.prompt_confirm = true; app.data.radarr_data.prompt_confirm = true;
app.data.radarr_data.selected_block = app.data.radarr_data.selected_block =
BlockSelectionState::new(&EDIT_COLLECTION_SELECTION_BLOCKS); BlockSelectionState::new(EDIT_COLLECTION_SELECTION_BLOCKS);
app app
.data .data
.radarr_data .radarr_data
.selected_block .selected_block
.set_index(EDIT_COLLECTION_SELECTION_BLOCKS.len() - 1); .set_index(0, EDIT_COLLECTION_SELECTION_BLOCKS.len() - 1);
EditCollectionHandler::with( EditCollectionHandler::with(
&SUBMIT_KEY, SUBMIT_KEY,
&mut app, &mut app,
&ActiveRadarrBlock::EditCollectionPrompt, ActiveRadarrBlock::EditCollectionPrompt,
&None, None,
) )
.handle(); .handle();
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
&ActiveRadarrBlock::Collections.into() ActiveRadarrBlock::Collections.into()
); );
assert_eq!( assert_eq!(
app.data.radarr_data.prompt_confirm_action, app.data.radarr_data.prompt_confirm_action,
Some(RadarrEvent::EditCollection(None)) Some(RadarrEvent::EditCollection(expected_edit_collection_params))
); );
assert!(app.should_refresh); assert!(app.should_refresh);
assert!(app.data.radarr_data.edit_collection_modal.is_none());
} }
#[test] #[test]
@@ -579,24 +589,24 @@ mod tests {
app.push_navigation_stack(ActiveRadarrBlock::EditCollectionPrompt.into()); app.push_navigation_stack(ActiveRadarrBlock::EditCollectionPrompt.into());
app.data.radarr_data.prompt_confirm = true; app.data.radarr_data.prompt_confirm = true;
app.data.radarr_data.selected_block = app.data.radarr_data.selected_block =
BlockSelectionState::new(&EDIT_COLLECTION_SELECTION_BLOCKS); BlockSelectionState::new(EDIT_COLLECTION_SELECTION_BLOCKS);
app app
.data .data
.radarr_data .radarr_data
.selected_block .selected_block
.set_index(EDIT_COLLECTION_SELECTION_BLOCKS.len() - 1); .set_index(0, EDIT_COLLECTION_SELECTION_BLOCKS.len() - 1);
EditCollectionHandler::with( EditCollectionHandler::with(
&SUBMIT_KEY, SUBMIT_KEY,
&mut app, &mut app,
&ActiveRadarrBlock::EditCollectionPrompt, ActiveRadarrBlock::EditCollectionPrompt,
&None, None,
) )
.handle(); .handle();
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
&ActiveRadarrBlock::EditCollectionPrompt.into() ActiveRadarrBlock::EditCollectionPrompt.into()
); );
assert_eq!(app.data.radarr_data.prompt_confirm_action, None); assert_eq!(app.data.radarr_data.prompt_confirm_action, None);
assert!(!app.should_refresh); assert!(!app.should_refresh);
@@ -611,18 +621,18 @@ mod tests {
let mut app = App::default(); let mut app = App::default();
app.data.radarr_data.edit_collection_modal = Some(EditCollectionModal::default()); app.data.radarr_data.edit_collection_modal = Some(EditCollectionModal::default());
app.data.radarr_data.selected_block = app.data.radarr_data.selected_block =
BlockSelectionState::new(&EDIT_COLLECTION_SELECTION_BLOCKS); BlockSelectionState::new(EDIT_COLLECTION_SELECTION_BLOCKS);
app.push_navigation_stack(current_route); app.push_navigation_stack(current_route);
EditCollectionHandler::with( EditCollectionHandler::with(
&SUBMIT_KEY, SUBMIT_KEY,
&mut app, &mut app,
&ActiveRadarrBlock::EditCollectionPrompt, ActiveRadarrBlock::EditCollectionPrompt,
&Some(ActiveRadarrBlock::Collections), Some(ActiveRadarrBlock::Collections),
) )
.handle(); .handle();
assert_eq!(app.get_current_route(), &current_route); assert_eq!(app.get_current_route(), current_route);
assert_eq!( assert_eq!(
app app
.data .data
@@ -635,14 +645,14 @@ mod tests {
); );
EditCollectionHandler::with( EditCollectionHandler::with(
&SUBMIT_KEY, SUBMIT_KEY,
&mut app, &mut app,
&ActiveRadarrBlock::EditCollectionPrompt, ActiveRadarrBlock::EditCollectionPrompt,
&Some(ActiveRadarrBlock::Collections), Some(ActiveRadarrBlock::Collections),
) )
.handle(); .handle();
assert_eq!(app.get_current_route(), &current_route); assert_eq!(app.get_current_route(), current_route);
assert_eq!( assert_eq!(
app app
.data .data
@@ -664,23 +674,23 @@ mod tests {
let mut app = App::default(); let mut app = App::default();
app.data.radarr_data.edit_collection_modal = Some(EditCollectionModal::default()); app.data.radarr_data.edit_collection_modal = Some(EditCollectionModal::default());
app.data.radarr_data.selected_block = app.data.radarr_data.selected_block =
BlockSelectionState::new(&EDIT_COLLECTION_SELECTION_BLOCKS); BlockSelectionState::new(EDIT_COLLECTION_SELECTION_BLOCKS);
app app
.data .data
.radarr_data .radarr_data
.selected_block .selected_block
.set_index(EDIT_COLLECTION_SELECTION_BLOCKS.len() - 2); .set_index(0, EDIT_COLLECTION_SELECTION_BLOCKS.len() - 2);
app.push_navigation_stack(current_route); app.push_navigation_stack(current_route);
EditCollectionHandler::with( EditCollectionHandler::with(
&SUBMIT_KEY, SUBMIT_KEY,
&mut app, &mut app,
&ActiveRadarrBlock::EditCollectionPrompt, ActiveRadarrBlock::EditCollectionPrompt,
&Some(ActiveRadarrBlock::Collections), Some(ActiveRadarrBlock::Collections),
) )
.handle(); .handle();
assert_eq!(app.get_current_route(), &current_route); assert_eq!(app.get_current_route(), current_route);
assert_eq!( assert_eq!(
app app
.data .data
@@ -693,14 +703,14 @@ mod tests {
); );
EditCollectionHandler::with( EditCollectionHandler::with(
&SUBMIT_KEY, SUBMIT_KEY,
&mut app, &mut app,
&ActiveRadarrBlock::EditCollectionPrompt, ActiveRadarrBlock::EditCollectionPrompt,
&Some(ActiveRadarrBlock::Collections), Some(ActiveRadarrBlock::Collections),
) )
.handle(); .handle();
assert_eq!(app.get_current_route(), &current_route); assert_eq!(app.get_current_route(), current_route);
assert_eq!( assert_eq!(
app app
.data .data
@@ -731,20 +741,20 @@ mod tests {
.into(), .into(),
); );
app.data.radarr_data.selected_block = app.data.radarr_data.selected_block =
BlockSelectionState::new(&EDIT_COLLECTION_SELECTION_BLOCKS); BlockSelectionState::new(EDIT_COLLECTION_SELECTION_BLOCKS);
app.data.radarr_data.selected_block.set_index(index); app.data.radarr_data.selected_block.set_index(0, index);
EditCollectionHandler::with( EditCollectionHandler::with(
&SUBMIT_KEY, SUBMIT_KEY,
&mut app, &mut app,
&ActiveRadarrBlock::EditCollectionPrompt, ActiveRadarrBlock::EditCollectionPrompt,
&Some(ActiveRadarrBlock::Collections), Some(ActiveRadarrBlock::Collections),
) )
.handle(); .handle();
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
&(selected_block, Some(ActiveRadarrBlock::Collections)).into() (selected_block, Some(ActiveRadarrBlock::Collections)).into()
); );
assert_eq!(app.data.radarr_data.prompt_confirm_action, None); assert_eq!(app.data.radarr_data.prompt_confirm_action, None);
@@ -768,16 +778,16 @@ mod tests {
app.push_navigation_stack(active_radarr_block.into()); app.push_navigation_stack(active_radarr_block.into());
EditCollectionHandler::with( EditCollectionHandler::with(
&SUBMIT_KEY, SUBMIT_KEY,
&mut app, &mut app,
&active_radarr_block, active_radarr_block,
&Some(ActiveRadarrBlock::Collections), Some(ActiveRadarrBlock::Collections),
) )
.handle(); .handle();
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
&ActiveRadarrBlock::EditCollectionPrompt.into() ActiveRadarrBlock::EditCollectionPrompt.into()
); );
if active_radarr_block == ActiveRadarrBlock::EditCollectionRootFolderPathInput { if active_radarr_block == ActiveRadarrBlock::EditCollectionRootFolderPathInput {
@@ -806,17 +816,17 @@ mod tests {
app.push_navigation_stack(ActiveRadarrBlock::EditCollectionRootFolderPathInput.into()); app.push_navigation_stack(ActiveRadarrBlock::EditCollectionRootFolderPathInput.into());
EditCollectionHandler::with( EditCollectionHandler::with(
&ESC_KEY, ESC_KEY,
&mut app, &mut app,
&ActiveRadarrBlock::EditCollectionRootFolderPathInput, ActiveRadarrBlock::EditCollectionRootFolderPathInput,
&None, None,
) )
.handle(); .handle();
assert!(!app.should_ignore_quit_key); assert!(!app.should_ignore_quit_key);
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
&ActiveRadarrBlock::EditCollectionPrompt.into() ActiveRadarrBlock::EditCollectionPrompt.into()
); );
} }
@@ -828,16 +838,16 @@ mod tests {
app.data.radarr_data = create_test_radarr_data(); app.data.radarr_data = create_test_radarr_data();
EditCollectionHandler::with( EditCollectionHandler::with(
&ESC_KEY, ESC_KEY,
&mut app, &mut app,
&ActiveRadarrBlock::EditCollectionPrompt, ActiveRadarrBlock::EditCollectionPrompt,
&None, None,
) )
.handle(); .handle();
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
&ActiveRadarrBlock::Collections.into() ActiveRadarrBlock::Collections.into()
); );
let radarr_data = &app.data.radarr_data; let radarr_data = &app.data.radarr_data;
@@ -860,11 +870,11 @@ mod tests {
app.push_navigation_stack(ActiveRadarrBlock::Collections.into()); app.push_navigation_stack(ActiveRadarrBlock::Collections.into());
app.push_navigation_stack(active_radarr_block.into()); app.push_navigation_stack(active_radarr_block.into());
EditCollectionHandler::with(&ESC_KEY, &mut app, &active_radarr_block, &None).handle(); EditCollectionHandler::with(ESC_KEY, &mut app, active_radarr_block, None).handle();
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
&ActiveRadarrBlock::Collections.into() ActiveRadarrBlock::Collections.into()
); );
} }
} }
@@ -890,10 +900,10 @@ mod tests {
}); });
EditCollectionHandler::with( EditCollectionHandler::with(
&DEFAULT_KEYBINDINGS.backspace.key, DEFAULT_KEYBINDINGS.backspace.key,
&mut app, &mut app,
&ActiveRadarrBlock::EditCollectionRootFolderPathInput, ActiveRadarrBlock::EditCollectionRootFolderPathInput,
&None, None,
) )
.handle(); .handle();
@@ -916,10 +926,10 @@ mod tests {
app.data.radarr_data.edit_collection_modal = Some(EditCollectionModal::default()); app.data.radarr_data.edit_collection_modal = Some(EditCollectionModal::default());
EditCollectionHandler::with( EditCollectionHandler::with(
&Key::Char('h'), Key::Char('h'),
&mut app, &mut app,
&ActiveRadarrBlock::EditCollectionRootFolderPathInput, ActiveRadarrBlock::EditCollectionRootFolderPathInput,
&None, None,
) )
.handle(); .handle();
@@ -939,34 +949,62 @@ mod tests {
#[test] #[test]
fn test_edit_collection_confirm_prompt_prompt_confirmation_confirm() { fn test_edit_collection_confirm_prompt_prompt_confirmation_confirm() {
let mut app = App::default(); let mut app = App::default();
app.data.radarr_data.edit_collection_modal = Some(EditCollectionModal::default()); let mut edit_collection_modal = EditCollectionModal {
path: "/nfs/Test Path".into(),
monitored: Some(false),
search_on_add: Some(false),
..EditCollectionModal::default()
};
edit_collection_modal
.quality_profile_list
.set_items(vec!["Any".to_owned(), "HD - 1080p".to_owned()]);
edit_collection_modal
.minimum_availability_list
.set_items(Vec::from_iter(MinimumAvailability::iter()));
app.data.radarr_data.edit_collection_modal = Some(edit_collection_modal);
app.data.radarr_data.collections.set_items(vec![Collection {
monitored: false,
search_on_add: false,
..collection()
}]);
app.data.radarr_data.quality_profile_map =
BiMap::from_iter([(1111, "Any".to_owned()), (2222, "HD - 1080p".to_owned())]);
let expected_edit_collection_params = EditCollectionParams {
collection_id: 123,
monitored: Some(false),
minimum_availability: Some(MinimumAvailability::Announced),
quality_profile_id: Some(1111),
root_folder_path: Some("/nfs/Test Path".to_owned()),
search_on_add: Some(false),
};
app.push_navigation_stack(ActiveRadarrBlock::Collections.into()); app.push_navigation_stack(ActiveRadarrBlock::Collections.into());
app.push_navigation_stack(ActiveRadarrBlock::EditCollectionPrompt.into()); app.push_navigation_stack(ActiveRadarrBlock::EditCollectionPrompt.into());
app.data.radarr_data.selected_block = app.data.radarr_data.selected_block =
BlockSelectionState::new(&EDIT_COLLECTION_SELECTION_BLOCKS); BlockSelectionState::new(EDIT_COLLECTION_SELECTION_BLOCKS);
app app
.data .data
.radarr_data .radarr_data
.selected_block .selected_block
.set_index(EDIT_COLLECTION_SELECTION_BLOCKS.len() - 1); .set_index(0, EDIT_COLLECTION_SELECTION_BLOCKS.len() - 1);
EditCollectionHandler::with( EditCollectionHandler::with(
&DEFAULT_KEYBINDINGS.confirm.key, DEFAULT_KEYBINDINGS.confirm.key,
&mut app, &mut app,
&ActiveRadarrBlock::EditCollectionPrompt, ActiveRadarrBlock::EditCollectionPrompt,
&None, None,
) )
.handle(); .handle();
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
&ActiveRadarrBlock::Collections.into() ActiveRadarrBlock::Collections.into()
); );
assert_eq!( assert_eq!(
app.data.radarr_data.prompt_confirm_action, app.data.radarr_data.prompt_confirm_action,
Some(RadarrEvent::EditCollection(None)) Some(RadarrEvent::EditCollection(expected_edit_collection_params))
); );
assert!(app.should_refresh); assert!(app.should_refresh);
assert!(app.data.radarr_data.edit_collection_modal.is_none());
} }
} }
@@ -974,23 +1012,67 @@ mod tests {
fn test_edit_collection_handler_accepts() { fn test_edit_collection_handler_accepts() {
ActiveRadarrBlock::iter().for_each(|active_radarr_block| { ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
if EDIT_COLLECTION_BLOCKS.contains(&active_radarr_block) { if EDIT_COLLECTION_BLOCKS.contains(&active_radarr_block) {
assert!(EditCollectionHandler::accepts(&active_radarr_block)); assert!(EditCollectionHandler::accepts(active_radarr_block));
} else { } else {
assert!(!EditCollectionHandler::accepts(&active_radarr_block)); assert!(!EditCollectionHandler::accepts(active_radarr_block));
} }
}); });
} }
#[test]
fn test_build_edit_collection_params() {
let mut app = App::default();
let mut edit_collection_modal = EditCollectionModal {
path: "/nfs/Test Path".into(),
monitored: Some(false),
search_on_add: Some(false),
..EditCollectionModal::default()
};
edit_collection_modal
.quality_profile_list
.set_items(vec!["Any".to_owned(), "HD - 1080p".to_owned()]);
edit_collection_modal
.minimum_availability_list
.set_items(Vec::from_iter(MinimumAvailability::iter()));
app.data.radarr_data.edit_collection_modal = Some(edit_collection_modal);
app.data.radarr_data.collections.set_items(vec![Collection {
monitored: false,
search_on_add: false,
..collection()
}]);
app.data.radarr_data.quality_profile_map =
BiMap::from_iter([(1111, "Any".to_owned()), (2222, "HD - 1080p".to_owned())]);
let expected_edit_collection_params = EditCollectionParams {
collection_id: 123,
monitored: Some(false),
minimum_availability: Some(MinimumAvailability::Announced),
quality_profile_id: Some(1111),
root_folder_path: Some("/nfs/Test Path".to_owned()),
search_on_add: Some(false),
};
let edit_collection_params = EditCollectionHandler::with(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveRadarrBlock::EditCollectionPrompt,
None,
)
.build_edit_collection_params();
assert_eq!(edit_collection_params, expected_edit_collection_params);
assert!(app.data.radarr_data.edit_collection_modal.is_none());
}
#[test] #[test]
fn test_edit_collection_handler_is_not_ready_when_loading() { fn test_edit_collection_handler_is_not_ready_when_loading() {
let mut app = App::default(); let mut app = App::default();
app.is_loading = true; app.is_loading = true;
let handler = EditCollectionHandler::with( let handler = EditCollectionHandler::with(
&DEFAULT_KEYBINDINGS.esc.key, DEFAULT_KEYBINDINGS.esc.key,
&mut app, &mut app,
&ActiveRadarrBlock::EditCollectionPrompt, ActiveRadarrBlock::EditCollectionPrompt,
&None, None,
); );
assert!(!handler.is_ready()); assert!(!handler.is_ready());
@@ -1002,10 +1084,10 @@ mod tests {
app.is_loading = false; app.is_loading = false;
let handler = EditCollectionHandler::with( let handler = EditCollectionHandler::with(
&DEFAULT_KEYBINDINGS.esc.key, DEFAULT_KEYBINDINGS.esc.key,
&mut app, &mut app,
&ActiveRadarrBlock::EditCollectionPrompt, ActiveRadarrBlock::EditCollectionPrompt,
&None, None,
); );
assert!(!handler.is_ready()); assert!(!handler.is_ready());
@@ -1018,10 +1100,10 @@ mod tests {
app.data.radarr_data.edit_collection_modal = Some(EditCollectionModal::default()); app.data.radarr_data.edit_collection_modal = Some(EditCollectionModal::default());
let handler = EditCollectionHandler::with( let handler = EditCollectionHandler::with(
&DEFAULT_KEYBINDINGS.esc.key, DEFAULT_KEYBINDINGS.esc.key,
&mut app, &mut app,
&ActiveRadarrBlock::EditCollectionPrompt, ActiveRadarrBlock::EditCollectionPrompt,
&None, None,
); );
assert!(handler.is_ready()); assert!(handler.is_ready());
+58 -274
View File
@@ -1,18 +1,19 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handle_table_events;
use crate::handlers::radarr_handlers::collections::collection_details_handler::CollectionDetailsHandler; use crate::handlers::radarr_handlers::collections::collection_details_handler::CollectionDetailsHandler;
use crate::handlers::radarr_handlers::collections::edit_collection_handler::EditCollectionHandler; use crate::handlers::radarr_handlers::collections::edit_collection_handler::EditCollectionHandler;
use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys; use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys;
use crate::handlers::table_handler::TableHandlingConfig;
use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler}; use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler};
use crate::models::radarr_models::Collection; use crate::models::radarr_models::Collection;
use crate::models::servarr_data::radarr::radarr_data::{ use crate::models::servarr_data::radarr::radarr_data::{
ActiveRadarrBlock, COLLECTIONS_BLOCKS, EDIT_COLLECTION_SELECTION_BLOCKS, ActiveRadarrBlock, COLLECTIONS_BLOCKS, EDIT_COLLECTION_SELECTION_BLOCKS,
}; };
use crate::models::stateful_table::SortOption; use crate::models::stateful_table::SortOption;
use crate::models::{BlockSelectionState, HorizontallyScrollableText, Scrollable}; use crate::models::BlockSelectionState;
use crate::network::radarr_network::RadarrEvent; use crate::network::radarr_network::RadarrEvent;
use crate::{handle_text_box_keys, handle_text_box_left_right_keys};
mod collection_details_handler; mod collection_details_handler;
mod edit_collection_handler; mod edit_collection_handler;
@@ -22,38 +23,66 @@ mod edit_collection_handler;
mod collections_handler_tests; mod collections_handler_tests;
pub(super) struct CollectionsHandler<'a, 'b> { pub(super) struct CollectionsHandler<'a, 'b> {
key: &'a Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
active_radarr_block: &'a ActiveRadarrBlock, active_radarr_block: ActiveRadarrBlock,
context: &'a Option<ActiveRadarrBlock>, context: Option<ActiveRadarrBlock>,
}
impl<'a, 'b> CollectionsHandler<'a, 'b> {
handle_table_events!(
self,
collections,
self.app.data.radarr_data.collections,
Collection
);
} }
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<'a, 'b> { impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<'a, 'b> {
fn handle(&mut self) { fn handle(&mut self) {
match self.active_radarr_block { let collections_table_handling_config =
_ if CollectionDetailsHandler::accepts(self.active_radarr_block) => { TableHandlingConfig::new(ActiveRadarrBlock::Collections.into())
CollectionDetailsHandler::with(self.key, self.app, self.active_radarr_block, self.context) .sorting_block(ActiveRadarrBlock::CollectionsSortPrompt.into())
.sort_by_fn(|a: &Collection, b: &Collection| a.id.cmp(&b.id))
.sort_options(collections_sorting_options())
.searching_block(ActiveRadarrBlock::SearchCollection.into())
.search_error_block(ActiveRadarrBlock::SearchCollectionError.into())
.search_field_fn(|collection| &collection.title.text)
.filtering_block(ActiveRadarrBlock::FilterCollections.into())
.filter_error_block(ActiveRadarrBlock::FilterCollectionsError.into())
.filter_field_fn(|collection| &collection.title.text);
if !self.handle_collections_table_events(collections_table_handling_config) {
match self.active_radarr_block {
_ if CollectionDetailsHandler::accepts(self.active_radarr_block) => {
CollectionDetailsHandler::with(
self.key,
self.app,
self.active_radarr_block,
self.context,
)
.handle(); .handle();
}
_ if EditCollectionHandler::accepts(self.active_radarr_block) => {
EditCollectionHandler::with(self.key, self.app, self.active_radarr_block, self.context)
.handle();
}
_ => self.handle_key_event(),
} }
_ if EditCollectionHandler::accepts(self.active_radarr_block) => {
EditCollectionHandler::with(self.key, self.app, self.active_radarr_block, self.context)
.handle();
}
_ => self.handle_key_event(),
} }
} }
fn accepts(active_block: &'a ActiveRadarrBlock) -> bool { fn accepts(active_block: ActiveRadarrBlock) -> bool {
CollectionDetailsHandler::accepts(active_block) CollectionDetailsHandler::accepts(active_block)
|| EditCollectionHandler::accepts(active_block) || EditCollectionHandler::accepts(active_block)
|| COLLECTIONS_BLOCKS.contains(active_block) || COLLECTIONS_BLOCKS.contains(&active_block)
} }
fn with( fn with(
key: &'a Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
active_block: &'a ActiveRadarrBlock, active_block: ActiveRadarrBlock,
context: &'a Option<ActiveRadarrBlock>, context: Option<ActiveRadarrBlock>,
) -> CollectionsHandler<'a, 'b> { ) -> CollectionsHandler<'a, 'b> {
CollectionsHandler { CollectionsHandler {
key, key,
@@ -63,7 +92,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<'
} }
} }
fn get_key(&self) -> &Key { fn get_key(&self) -> Key {
self.key self.key
} }
@@ -71,105 +100,13 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<'
!self.app.is_loading && !self.app.data.radarr_data.collections.is_empty() !self.app.is_loading && !self.app.data.radarr_data.collections.is_empty()
} }
fn handle_scroll_up(&mut self) { fn handle_scroll_up(&mut self) {}
match self.active_radarr_block {
ActiveRadarrBlock::Collections => self.app.data.radarr_data.collections.scroll_up(),
ActiveRadarrBlock::CollectionsSortPrompt => self
.app
.data
.radarr_data
.collections
.sort
.as_mut()
.unwrap()
.scroll_up(),
_ => (),
}
}
fn handle_scroll_down(&mut self) { fn handle_scroll_down(&mut self) {}
match self.active_radarr_block {
ActiveRadarrBlock::Collections => self.app.data.radarr_data.collections.scroll_down(),
ActiveRadarrBlock::CollectionsSortPrompt => self
.app
.data
.radarr_data
.collections
.sort
.as_mut()
.unwrap()
.scroll_down(),
_ => (),
}
}
fn handle_home(&mut self) { fn handle_home(&mut self) {}
match self.active_radarr_block {
ActiveRadarrBlock::Collections => self.app.data.radarr_data.collections.scroll_to_top(),
ActiveRadarrBlock::SearchCollection => self
.app
.data
.radarr_data
.collections
.search
.as_mut()
.unwrap()
.scroll_home(),
ActiveRadarrBlock::FilterCollections => self
.app
.data
.radarr_data
.collections
.filter
.as_mut()
.unwrap()
.scroll_home(),
ActiveRadarrBlock::CollectionsSortPrompt => self
.app
.data
.radarr_data
.collections
.sort
.as_mut()
.unwrap()
.scroll_to_top(),
_ => (),
}
}
fn handle_end(&mut self) { fn handle_end(&mut self) {}
match self.active_radarr_block {
ActiveRadarrBlock::Collections => self.app.data.radarr_data.collections.scroll_to_bottom(),
ActiveRadarrBlock::SearchCollection => self
.app
.data
.radarr_data
.collections
.search
.as_mut()
.unwrap()
.reset_offset(),
ActiveRadarrBlock::FilterCollections => self
.app
.data
.radarr_data
.collections
.filter
.as_mut()
.unwrap()
.reset_offset(),
ActiveRadarrBlock::CollectionsSortPrompt => self
.app
.data
.radarr_data
.collections
.sort
.as_mut()
.unwrap()
.scroll_to_bottom(),
_ => (),
}
}
fn handle_delete(&mut self) {} fn handle_delete(&mut self) {}
@@ -177,34 +114,6 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<'
match self.active_radarr_block { match self.active_radarr_block {
ActiveRadarrBlock::Collections => handle_change_tab_left_right_keys(self.app, self.key), ActiveRadarrBlock::Collections => handle_change_tab_left_right_keys(self.app, self.key),
ActiveRadarrBlock::UpdateAllCollectionsPrompt => handle_prompt_toggle(self.app, self.key), ActiveRadarrBlock::UpdateAllCollectionsPrompt => handle_prompt_toggle(self.app, self.key),
ActiveRadarrBlock::SearchCollection => {
handle_text_box_left_right_keys!(
self,
self.key,
self
.app
.data
.radarr_data
.collections
.search
.as_mut()
.unwrap()
)
}
ActiveRadarrBlock::FilterCollections => {
handle_text_box_left_right_keys!(
self,
self.key,
self
.app
.data
.radarr_data
.collections
.filter
.as_mut()
.unwrap()
)
}
_ => (), _ => (),
} }
} }
@@ -214,44 +123,6 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<'
ActiveRadarrBlock::Collections => self ActiveRadarrBlock::Collections => self
.app .app
.push_navigation_stack(ActiveRadarrBlock::CollectionDetails.into()), .push_navigation_stack(ActiveRadarrBlock::CollectionDetails.into()),
ActiveRadarrBlock::SearchCollection => {
self.app.pop_navigation_stack();
self.app.should_ignore_quit_key = false;
if self.app.data.radarr_data.collections.search.is_some() {
let has_match = self
.app
.data
.radarr_data
.collections
.apply_search(|collection| &collection.title.text);
if !has_match {
self
.app
.push_navigation_stack(ActiveRadarrBlock::SearchCollectionError.into());
}
}
}
ActiveRadarrBlock::FilterCollections => {
self.app.pop_navigation_stack();
self.app.should_ignore_quit_key = false;
if self.app.data.radarr_data.collections.filter.is_some() {
let has_matches = self
.app
.data
.radarr_data
.collections
.apply_filter(|collection| &collection.title.text);
if !has_matches {
self
.app
.push_navigation_stack(ActiveRadarrBlock::FilterCollectionsError.into());
}
}
}
ActiveRadarrBlock::UpdateAllCollectionsPrompt => { ActiveRadarrBlock::UpdateAllCollectionsPrompt => {
if self.app.data.radarr_data.prompt_confirm { if self.app.data.radarr_data.prompt_confirm {
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateCollections); self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateCollections);
@@ -259,44 +130,17 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<'
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
} }
ActiveRadarrBlock::CollectionsSortPrompt => {
self
.app
.data
.radarr_data
.collections
.items
.sort_by(|a, b| a.id.cmp(&b.id));
self.app.data.radarr_data.collections.apply_sorting();
self.app.pop_navigation_stack();
}
_ => (), _ => (),
} }
} }
fn handle_esc(&mut self) { fn handle_esc(&mut self) {
match self.active_radarr_block { match self.active_radarr_block {
ActiveRadarrBlock::FilterCollections | ActiveRadarrBlock::FilterCollectionsError => {
self.app.pop_navigation_stack();
self.app.data.radarr_data.collections.reset_filter();
self.app.should_ignore_quit_key = false;
}
ActiveRadarrBlock::SearchCollection | ActiveRadarrBlock::SearchCollectionError => {
self.app.pop_navigation_stack();
self.app.data.radarr_data.collections.reset_search();
self.app.should_ignore_quit_key = false;
}
ActiveRadarrBlock::UpdateAllCollectionsPrompt => { ActiveRadarrBlock::UpdateAllCollectionsPrompt => {
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
self.app.data.radarr_data.prompt_confirm = false; self.app.data.radarr_data.prompt_confirm = false;
} }
ActiveRadarrBlock::CollectionsSortPrompt => {
self.app.pop_navigation_stack();
}
_ => { _ => {
self.app.data.radarr_data.collections.reset_search();
self.app.data.radarr_data.collections.reset_filter();
handle_clear_errors(self.app); handle_clear_errors(self.app);
} }
} }
@@ -306,87 +150,27 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<'
let key = self.key; let key = self.key;
match self.active_radarr_block { match self.active_radarr_block {
ActiveRadarrBlock::Collections => match self.key { ActiveRadarrBlock::Collections => match self.key {
_ if *key == DEFAULT_KEYBINDINGS.search.key => { _ if key == DEFAULT_KEYBINDINGS.edit.key => {
self self
.app .app
.push_navigation_stack(ActiveRadarrBlock::SearchCollection.into()); .push_navigation_stack(ActiveRadarrBlock::EditCollectionPrompt.into());
self.app.data.radarr_data.collections.search =
Some(HorizontallyScrollableText::default());
self.app.should_ignore_quit_key = true;
}
_ if *key == DEFAULT_KEYBINDINGS.filter.key => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::FilterCollections.into());
self.app.data.radarr_data.collections.reset_filter();
self.app.data.radarr_data.collections.filter =
Some(HorizontallyScrollableText::default());
self.app.should_ignore_quit_key = true;
}
_ if *key == DEFAULT_KEYBINDINGS.edit.key => {
self.app.push_navigation_stack(
(
ActiveRadarrBlock::EditCollectionPrompt,
Some(ActiveRadarrBlock::Collections),
)
.into(),
);
self.app.data.radarr_data.edit_collection_modal = self.app.data.radarr_data.edit_collection_modal =
Some((&self.app.data.radarr_data).into()); Some((&self.app.data.radarr_data).into());
self.app.data.radarr_data.selected_block = self.app.data.radarr_data.selected_block =
BlockSelectionState::new(&EDIT_COLLECTION_SELECTION_BLOCKS); BlockSelectionState::new(EDIT_COLLECTION_SELECTION_BLOCKS);
} }
_ if *key == DEFAULT_KEYBINDINGS.update.key => { _ if key == DEFAULT_KEYBINDINGS.update.key => {
self self
.app .app
.push_navigation_stack(ActiveRadarrBlock::UpdateAllCollectionsPrompt.into()); .push_navigation_stack(ActiveRadarrBlock::UpdateAllCollectionsPrompt.into());
} }
_ if *key == DEFAULT_KEYBINDINGS.refresh.key => { _ if key == DEFAULT_KEYBINDINGS.refresh.key => {
self.app.should_refresh = true; self.app.should_refresh = true;
} }
_ if *key == DEFAULT_KEYBINDINGS.sort.key => {
self
.app
.data
.radarr_data
.collections
.sorting(collections_sorting_options());
self
.app
.push_navigation_stack(ActiveRadarrBlock::CollectionsSortPrompt.into());
}
_ => (), _ => (),
}, },
ActiveRadarrBlock::SearchCollection => {
handle_text_box_keys!(
self,
key,
self
.app
.data
.radarr_data
.collections
.search
.as_mut()
.unwrap()
)
}
ActiveRadarrBlock::FilterCollections => {
handle_text_box_keys!(
self,
key,
self
.app
.data
.radarr_data
.collections
.filter
.as_mut()
.unwrap()
)
}
ActiveRadarrBlock::UpdateAllCollectionsPrompt => { ActiveRadarrBlock::UpdateAllCollectionsPrompt => {
if *key == DEFAULT_KEYBINDINGS.confirm.key { if key == DEFAULT_KEYBINDINGS.confirm.key {
self.app.data.radarr_data.prompt_confirm = true; self.app.data.radarr_data.prompt_confirm = true;
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateCollections); self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateCollections);
@@ -1,119 +1,17 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use pretty_assertions::assert_str_eq; use pretty_assertions::assert_eq;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::radarr_handlers::downloads::DownloadsHandler; use crate::handlers::radarr_handlers::downloads::DownloadsHandler;
use crate::handlers::radarr_handlers::radarr_handler_test_utils::utils::download_record;
use crate::handlers::KeyEventHandler; use crate::handlers::KeyEventHandler;
use crate::models::radarr_models::DownloadRecord; use crate::models::radarr_models::DownloadRecord;
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, DOWNLOADS_BLOCKS}; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, DOWNLOADS_BLOCKS};
mod test_handle_scroll_up_and_down {
use rstest::rstest;
use crate::models::radarr_models::DownloadRecord;
use crate::{simple_stateful_iterable_vec, test_iterable_scroll};
use super::*;
test_iterable_scroll!(
test_downloads_scroll,
DownloadsHandler,
downloads,
DownloadRecord,
ActiveRadarrBlock::Downloads,
None,
title
);
#[rstest]
fn test_downloads_scroll_no_op_when_not_ready(
#[values(
DEFAULT_KEYBINDINGS.up.key, DEFAULT_KEYBINDINGS.down.key
)]
key: Key,
) {
let mut app = App::default();
app.is_loading = true;
app
.data
.radarr_data
.downloads
.set_items(simple_stateful_iterable_vec!(DownloadRecord));
DownloadsHandler::with(&key, &mut app, &ActiveRadarrBlock::Downloads, &None).handle();
assert_str_eq!(
app.data.radarr_data.downloads.current_selection().title,
"Test 1"
);
DownloadsHandler::with(&key, &mut app, &ActiveRadarrBlock::Downloads, &None).handle();
assert_str_eq!(
app.data.radarr_data.downloads.current_selection().title,
"Test 1"
);
}
}
mod test_handle_home_end {
use crate::models::radarr_models::DownloadRecord;
use crate::{extended_stateful_iterable_vec, test_iterable_home_and_end};
use super::*;
test_iterable_home_and_end!(
test_downloads_home_end,
DownloadsHandler,
downloads,
DownloadRecord,
ActiveRadarrBlock::Downloads,
None,
title
);
#[test]
fn test_downloads_home_end_no_op_when_not_ready() {
let mut app = App::default();
app.is_loading = true;
app
.data
.radarr_data
.downloads
.set_items(extended_stateful_iterable_vec!(DownloadRecord));
DownloadsHandler::with(
&DEFAULT_KEYBINDINGS.end.key,
&mut app,
&ActiveRadarrBlock::Downloads,
&None,
)
.handle();
assert_str_eq!(
app.data.radarr_data.downloads.current_selection().title,
"Test 1"
);
DownloadsHandler::with(
&DEFAULT_KEYBINDINGS.home.key,
&mut app,
&ActiveRadarrBlock::Downloads,
&None,
)
.handle();
assert_str_eq!(
app.data.radarr_data.downloads.current_selection().title,
"Test 1"
);
}
}
mod test_handle_delete { mod test_handle_delete {
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
@@ -130,11 +28,11 @@ mod tests {
.downloads .downloads
.set_items(vec![DownloadRecord::default()]); .set_items(vec![DownloadRecord::default()]);
DownloadsHandler::with(&DELETE_KEY, &mut app, &ActiveRadarrBlock::Downloads, &None).handle(); DownloadsHandler::with(DELETE_KEY, &mut app, ActiveRadarrBlock::Downloads, None).handle();
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
&ActiveRadarrBlock::DeleteDownloadPrompt.into() ActiveRadarrBlock::DeleteDownloadPrompt.into()
); );
} }
@@ -149,12 +47,9 @@ mod tests {
.downloads .downloads
.set_items(vec![DownloadRecord::default()]); .set_items(vec![DownloadRecord::default()]);
DownloadsHandler::with(&DELETE_KEY, &mut app, &ActiveRadarrBlock::Downloads, &None).handle(); DownloadsHandler::with(DELETE_KEY, &mut app, ActiveRadarrBlock::Downloads, None).handle();
assert_eq!( assert_eq!(app.get_current_route(), ActiveRadarrBlock::Downloads.into());
app.get_current_route(),
&ActiveRadarrBlock::Downloads.into()
);
} }
} }
@@ -171,20 +66,20 @@ mod tests {
app.data.radarr_data.main_tabs.set_index(2); app.data.radarr_data.main_tabs.set_index(2);
DownloadsHandler::with( DownloadsHandler::with(
&DEFAULT_KEYBINDINGS.left.key, DEFAULT_KEYBINDINGS.left.key,
&mut app, &mut app,
&ActiveRadarrBlock::Downloads, ActiveRadarrBlock::Downloads,
&None, None,
) )
.handle(); .handle();
assert_eq!( assert_eq!(
app.data.radarr_data.main_tabs.get_active_route(), app.data.radarr_data.main_tabs.get_active_route(),
&ActiveRadarrBlock::Collections.into() ActiveRadarrBlock::Collections.into()
); );
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
&ActiveRadarrBlock::Collections.into() ActiveRadarrBlock::Collections.into()
); );
} }
@@ -195,21 +90,18 @@ mod tests {
app.data.radarr_data.main_tabs.set_index(2); app.data.radarr_data.main_tabs.set_index(2);
DownloadsHandler::with( DownloadsHandler::with(
&DEFAULT_KEYBINDINGS.right.key, DEFAULT_KEYBINDINGS.right.key,
&mut app, &mut app,
&ActiveRadarrBlock::Downloads, ActiveRadarrBlock::Downloads,
&None, None,
) )
.handle(); .handle();
assert_eq!( assert_eq!(
app.data.radarr_data.main_tabs.get_active_route(), app.data.radarr_data.main_tabs.get_active_route(),
&ActiveRadarrBlock::Blocklist.into() ActiveRadarrBlock::Blocklist.into()
);
assert_eq!(
app.get_current_route(),
&ActiveRadarrBlock::Blocklist.into()
); );
assert_eq!(app.get_current_route(), ActiveRadarrBlock::Blocklist.into());
} }
#[rstest] #[rstest]
@@ -223,11 +115,11 @@ mod tests {
) { ) {
let mut app = App::default(); let mut app = App::default();
DownloadsHandler::with(&key, &mut app, &active_radarr_block, &None).handle(); DownloadsHandler::with(key, &mut app, active_radarr_block, None).handle();
assert!(app.data.radarr_data.prompt_confirm); assert!(app.data.radarr_data.prompt_confirm);
DownloadsHandler::with(&key, &mut app, &active_radarr_block, &None).handle(); DownloadsHandler::with(key, &mut app, active_radarr_block, None).handle();
assert!(!app.data.radarr_data.prompt_confirm); assert!(!app.data.radarr_data.prompt_confirm);
} }
@@ -247,7 +139,7 @@ mod tests {
#[case( #[case(
ActiveRadarrBlock::Downloads, ActiveRadarrBlock::Downloads,
ActiveRadarrBlock::DeleteDownloadPrompt, ActiveRadarrBlock::DeleteDownloadPrompt,
RadarrEvent::DeleteDownload(None) RadarrEvent::DeleteDownload(1)
)] )]
#[case( #[case(
ActiveRadarrBlock::Downloads, ActiveRadarrBlock::Downloads,
@@ -264,19 +156,19 @@ mod tests {
.data .data
.radarr_data .radarr_data
.downloads .downloads
.set_items(vec![DownloadRecord::default()]); .set_items(vec![download_record()]);
app.data.radarr_data.prompt_confirm = true; app.data.radarr_data.prompt_confirm = true;
app.push_navigation_stack(base_route.into()); app.push_navigation_stack(base_route.into());
app.push_navigation_stack(prompt_block.into()); app.push_navigation_stack(prompt_block.into());
DownloadsHandler::with(&SUBMIT_KEY, &mut app, &prompt_block, &None).handle(); DownloadsHandler::with(SUBMIT_KEY, &mut app, prompt_block, None).handle();
assert!(app.data.radarr_data.prompt_confirm); assert!(app.data.radarr_data.prompt_confirm);
assert_eq!( assert_eq!(
app.data.radarr_data.prompt_confirm_action, app.data.radarr_data.prompt_confirm_action,
Some(expected_action) Some(expected_action)
); );
assert_eq!(app.get_current_route(), &base_route.into()); assert_eq!(app.get_current_route(), base_route.into());
} }
#[rstest] #[rstest]
@@ -295,11 +187,11 @@ mod tests {
app.push_navigation_stack(base_route.into()); app.push_navigation_stack(base_route.into());
app.push_navigation_stack(prompt_block.into()); app.push_navigation_stack(prompt_block.into());
DownloadsHandler::with(&SUBMIT_KEY, &mut app, &prompt_block, &None).handle(); DownloadsHandler::with(SUBMIT_KEY, &mut app, prompt_block, None).handle();
assert!(!app.data.radarr_data.prompt_confirm); assert!(!app.data.radarr_data.prompt_confirm);
assert_eq!(app.data.radarr_data.prompt_confirm_action, None); assert_eq!(app.data.radarr_data.prompt_confirm_action, None);
assert_eq!(app.get_current_route(), &base_route.into()); assert_eq!(app.get_current_route(), base_route.into());
} }
} }
@@ -323,9 +215,9 @@ mod tests {
app.push_navigation_stack(prompt_block.into()); app.push_navigation_stack(prompt_block.into());
app.data.radarr_data.prompt_confirm = true; app.data.radarr_data.prompt_confirm = true;
DownloadsHandler::with(&ESC_KEY, &mut app, &prompt_block, &None).handle(); DownloadsHandler::with(ESC_KEY, &mut app, prompt_block, None).handle();
assert_eq!(app.get_current_route(), &base_block.into()); assert_eq!(app.get_current_route(), base_block.into());
assert!(!app.data.radarr_data.prompt_confirm); assert!(!app.data.radarr_data.prompt_confirm);
} }
@@ -337,12 +229,9 @@ mod tests {
app.push_navigation_stack(ActiveRadarrBlock::Downloads.into()); app.push_navigation_stack(ActiveRadarrBlock::Downloads.into());
app.push_navigation_stack(ActiveRadarrBlock::Downloads.into()); app.push_navigation_stack(ActiveRadarrBlock::Downloads.into());
DownloadsHandler::with(&ESC_KEY, &mut app, &ActiveRadarrBlock::Downloads, &None).handle(); DownloadsHandler::with(ESC_KEY, &mut app, ActiveRadarrBlock::Downloads, None).handle();
assert_eq!( assert_eq!(app.get_current_route(), ActiveRadarrBlock::Downloads.into());
app.get_current_route(),
&ActiveRadarrBlock::Downloads.into()
);
assert!(app.error.text.is_empty()); assert!(app.error.text.is_empty());
} }
} }
@@ -365,16 +254,16 @@ mod tests {
.set_items(vec![DownloadRecord::default()]); .set_items(vec![DownloadRecord::default()]);
DownloadsHandler::with( DownloadsHandler::with(
&DEFAULT_KEYBINDINGS.update.key, DEFAULT_KEYBINDINGS.update.key,
&mut app, &mut app,
&ActiveRadarrBlock::Downloads, ActiveRadarrBlock::Downloads,
&None, None,
) )
.handle(); .handle();
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
&ActiveRadarrBlock::UpdateDownloadsPrompt.into() ActiveRadarrBlock::UpdateDownloadsPrompt.into()
); );
} }
@@ -390,17 +279,14 @@ mod tests {
.set_items(vec![DownloadRecord::default()]); .set_items(vec![DownloadRecord::default()]);
DownloadsHandler::with( DownloadsHandler::with(
&DEFAULT_KEYBINDINGS.update.key, DEFAULT_KEYBINDINGS.update.key,
&mut app, &mut app,
&ActiveRadarrBlock::Downloads, ActiveRadarrBlock::Downloads,
&None, None,
) )
.handle(); .handle();
assert_eq!( assert_eq!(app.get_current_route(), ActiveRadarrBlock::Downloads.into());
app.get_current_route(),
&ActiveRadarrBlock::Downloads.into()
);
} }
#[test] #[test]
@@ -414,17 +300,14 @@ mod tests {
app.push_navigation_stack(ActiveRadarrBlock::Downloads.into()); app.push_navigation_stack(ActiveRadarrBlock::Downloads.into());
DownloadsHandler::with( DownloadsHandler::with(
&DEFAULT_KEYBINDINGS.refresh.key, DEFAULT_KEYBINDINGS.refresh.key,
&mut app, &mut app,
&ActiveRadarrBlock::Downloads, ActiveRadarrBlock::Downloads,
&None, None,
) )
.handle(); .handle();
assert_eq!( assert_eq!(app.get_current_route(), ActiveRadarrBlock::Downloads.into());
app.get_current_route(),
&ActiveRadarrBlock::Downloads.into()
);
assert!(app.should_refresh); assert!(app.should_refresh);
} }
@@ -440,17 +323,14 @@ mod tests {
.set_items(vec![DownloadRecord::default()]); .set_items(vec![DownloadRecord::default()]);
DownloadsHandler::with( DownloadsHandler::with(
&DEFAULT_KEYBINDINGS.refresh.key, DEFAULT_KEYBINDINGS.refresh.key,
&mut app, &mut app,
&ActiveRadarrBlock::Downloads, ActiveRadarrBlock::Downloads,
&None, None,
) )
.handle(); .handle();
assert_eq!( assert_eq!(app.get_current_route(), ActiveRadarrBlock::Downloads.into());
app.get_current_route(),
&ActiveRadarrBlock::Downloads.into()
);
assert!(!app.should_refresh); assert!(!app.should_refresh);
} }
@@ -458,7 +338,7 @@ mod tests {
#[case( #[case(
ActiveRadarrBlock::Downloads, ActiveRadarrBlock::Downloads,
ActiveRadarrBlock::DeleteDownloadPrompt, ActiveRadarrBlock::DeleteDownloadPrompt,
RadarrEvent::DeleteDownload(None) RadarrEvent::DeleteDownload(1)
)] )]
#[case( #[case(
ActiveRadarrBlock::Downloads, ActiveRadarrBlock::Downloads,
@@ -475,15 +355,15 @@ mod tests {
.data .data
.radarr_data .radarr_data
.downloads .downloads
.set_items(vec![DownloadRecord::default()]); .set_items(vec![download_record()]);
app.push_navigation_stack(base_route.into()); app.push_navigation_stack(base_route.into());
app.push_navigation_stack(prompt_block.into()); app.push_navigation_stack(prompt_block.into());
DownloadsHandler::with( DownloadsHandler::with(
&DEFAULT_KEYBINDINGS.confirm.key, DEFAULT_KEYBINDINGS.confirm.key,
&mut app, &mut app,
&prompt_block, prompt_block,
&None, None,
) )
.handle(); .handle();
@@ -492,7 +372,7 @@ mod tests {
app.data.radarr_data.prompt_confirm_action, app.data.radarr_data.prompt_confirm_action,
Some(expected_action) Some(expected_action)
); );
assert_eq!(app.get_current_route(), &base_route.into()); assert_eq!(app.get_current_route(), base_route.into());
} }
} }
@@ -500,23 +380,43 @@ mod tests {
fn test_downloads_handler_accepts() { fn test_downloads_handler_accepts() {
ActiveRadarrBlock::iter().for_each(|active_radarr_block| { ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
if DOWNLOADS_BLOCKS.contains(&active_radarr_block) { if DOWNLOADS_BLOCKS.contains(&active_radarr_block) {
assert!(DownloadsHandler::accepts(&active_radarr_block)); assert!(DownloadsHandler::accepts(active_radarr_block));
} else { } else {
assert!(!DownloadsHandler::accepts(&active_radarr_block)); assert!(!DownloadsHandler::accepts(active_radarr_block));
} }
}) })
} }
#[test]
fn test_extract_download_id() {
let mut app = App::default();
app
.data
.radarr_data
.downloads
.set_items(vec![download_record()]);
let download_id = DownloadsHandler::with(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveRadarrBlock::Downloads,
None,
)
.extract_download_id();
assert_eq!(download_id, 1);
}
#[test] #[test]
fn test_downloads_handler_not_ready_when_loading() { fn test_downloads_handler_not_ready_when_loading() {
let mut app = App::default(); let mut app = App::default();
app.is_loading = true; app.is_loading = true;
let handler = DownloadsHandler::with( let handler = DownloadsHandler::with(
&DEFAULT_KEYBINDINGS.esc.key, DEFAULT_KEYBINDINGS.esc.key,
&mut app, &mut app,
&ActiveRadarrBlock::Downloads, ActiveRadarrBlock::Downloads,
&None, None,
); );
assert!(!handler.is_ready()); assert!(!handler.is_ready());
@@ -528,10 +428,10 @@ mod tests {
app.is_loading = false; app.is_loading = false;
let handler = DownloadsHandler::with( let handler = DownloadsHandler::with(
&DEFAULT_KEYBINDINGS.esc.key, DEFAULT_KEYBINDINGS.esc.key,
&mut app, &mut app,
&ActiveRadarrBlock::Downloads, ActiveRadarrBlock::Downloads,
&None, None,
); );
assert!(!handler.is_ready()); assert!(!handler.is_ready());
@@ -548,10 +448,10 @@ mod tests {
.downloads .downloads
.set_items(vec![DownloadRecord::default()]); .set_items(vec![DownloadRecord::default()]);
let handler = DownloadsHandler::with( let handler = DownloadsHandler::with(
&DEFAULT_KEYBINDINGS.esc.key, DEFAULT_KEYBINDINGS.esc.key,
&mut app, &mut app,
&ActiveRadarrBlock::Downloads, ActiveRadarrBlock::Downloads,
&None, None,
); );
assert!(handler.is_ready()); assert!(handler.is_ready());
+47 -37
View File
@@ -1,10 +1,12 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handle_table_events;
use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys; use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys;
use crate::handlers::table_handler::TableHandlingConfig;
use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler}; use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler};
use crate::models::radarr_models::DownloadRecord;
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, DOWNLOADS_BLOCKS}; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, DOWNLOADS_BLOCKS};
use crate::models::Scrollable;
use crate::network::radarr_network::RadarrEvent; use crate::network::radarr_network::RadarrEvent;
#[cfg(test)] #[cfg(test)]
@@ -12,22 +14,44 @@ use crate::network::radarr_network::RadarrEvent;
mod downloads_handler_tests; mod downloads_handler_tests;
pub(super) struct DownloadsHandler<'a, 'b> { pub(super) struct DownloadsHandler<'a, 'b> {
key: &'a Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
active_radarr_block: &'a ActiveRadarrBlock, active_radarr_block: ActiveRadarrBlock,
_context: &'a Option<ActiveRadarrBlock>, _context: Option<ActiveRadarrBlock>,
}
impl<'a, 'b> DownloadsHandler<'a, 'b> {
handle_table_events!(
self,
downloads,
self.app.data.radarr_data.downloads,
DownloadRecord
);
fn extract_download_id(&self) -> i64 {
self.app.data.radarr_data.downloads.current_selection().id
}
} }
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DownloadsHandler<'a, 'b> { impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DownloadsHandler<'a, 'b> {
fn accepts(active_block: &'a ActiveRadarrBlock) -> bool { fn handle(&mut self) {
DOWNLOADS_BLOCKS.contains(active_block) let downloads_table_handling_config =
TableHandlingConfig::new(ActiveRadarrBlock::Downloads.into());
if !self.handle_downloads_table_events(downloads_table_handling_config) {
self.handle_key_event();
}
}
fn accepts(active_block: ActiveRadarrBlock) -> bool {
DOWNLOADS_BLOCKS.contains(&active_block)
} }
fn with( fn with(
key: &'a Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
active_block: &'a ActiveRadarrBlock, active_block: ActiveRadarrBlock,
_context: &'a Option<ActiveRadarrBlock>, _context: Option<ActiveRadarrBlock>,
) -> DownloadsHandler<'a, 'b> { ) -> DownloadsHandler<'a, 'b> {
DownloadsHandler { DownloadsHandler {
key, key,
@@ -37,7 +61,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DownloadsHandler<'a,
} }
} }
fn get_key(&self) -> &Key { fn get_key(&self) -> Key {
self.key self.key
} }
@@ -45,32 +69,16 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DownloadsHandler<'a,
!self.app.is_loading && !self.app.data.radarr_data.downloads.is_empty() !self.app.is_loading && !self.app.data.radarr_data.downloads.is_empty()
} }
fn handle_scroll_up(&mut self) { fn handle_scroll_up(&mut self) {}
if self.active_radarr_block == &ActiveRadarrBlock::Downloads {
self.app.data.radarr_data.downloads.scroll_up()
}
}
fn handle_scroll_down(&mut self) { fn handle_scroll_down(&mut self) {}
if self.active_radarr_block == &ActiveRadarrBlock::Downloads {
self.app.data.radarr_data.downloads.scroll_down()
}
}
fn handle_home(&mut self) { fn handle_home(&mut self) {}
if self.active_radarr_block == &ActiveRadarrBlock::Downloads {
self.app.data.radarr_data.downloads.scroll_to_top()
}
}
fn handle_end(&mut self) { fn handle_end(&mut self) {}
if self.active_radarr_block == &ActiveRadarrBlock::Downloads {
self.app.data.radarr_data.downloads.scroll_to_bottom()
}
}
fn handle_delete(&mut self) { fn handle_delete(&mut self) {
if self.active_radarr_block == &ActiveRadarrBlock::Downloads { if self.active_radarr_block == ActiveRadarrBlock::Downloads {
self self
.app .app
.push_navigation_stack(ActiveRadarrBlock::DeleteDownloadPrompt.into()) .push_navigation_stack(ActiveRadarrBlock::DeleteDownloadPrompt.into())
@@ -91,7 +99,8 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DownloadsHandler<'a,
match self.active_radarr_block { match self.active_radarr_block {
ActiveRadarrBlock::DeleteDownloadPrompt => { ActiveRadarrBlock::DeleteDownloadPrompt => {
if self.app.data.radarr_data.prompt_confirm { if self.app.data.radarr_data.prompt_confirm {
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DeleteDownload(None)); self.app.data.radarr_data.prompt_confirm_action =
Some(RadarrEvent::DeleteDownload(self.extract_download_id()));
} }
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
@@ -121,26 +130,27 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DownloadsHandler<'a,
let key = self.key; let key = self.key;
match self.active_radarr_block { match self.active_radarr_block {
ActiveRadarrBlock::Downloads => match self.key { ActiveRadarrBlock::Downloads => match self.key {
_ if *key == DEFAULT_KEYBINDINGS.update.key => { _ if key == DEFAULT_KEYBINDINGS.update.key => {
self self
.app .app
.push_navigation_stack(ActiveRadarrBlock::UpdateDownloadsPrompt.into()); .push_navigation_stack(ActiveRadarrBlock::UpdateDownloadsPrompt.into());
} }
_ if *key == DEFAULT_KEYBINDINGS.refresh.key => { _ if key == DEFAULT_KEYBINDINGS.refresh.key => {
self.app.should_refresh = true; self.app.should_refresh = true;
} }
_ => (), _ => (),
}, },
ActiveRadarrBlock::DeleteDownloadPrompt => { ActiveRadarrBlock::DeleteDownloadPrompt => {
if *key == DEFAULT_KEYBINDINGS.confirm.key { if key == DEFAULT_KEYBINDINGS.confirm.key {
self.app.data.radarr_data.prompt_confirm = true; self.app.data.radarr_data.prompt_confirm = true;
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DeleteDownload(None)); self.app.data.radarr_data.prompt_confirm_action =
Some(RadarrEvent::DeleteDownload(self.extract_download_id()));
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
} }
} }
ActiveRadarrBlock::UpdateDownloadsPrompt => { ActiveRadarrBlock::UpdateDownloadsPrompt => {
if *key == DEFAULT_KEYBINDINGS.confirm.key { if key == DEFAULT_KEYBINDINGS.confirm.key {
self.app.data.radarr_data.prompt_confirm = true; self.app.data.radarr_data.prompt_confirm = true;
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateDownloads); self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateDownloads);
@@ -2,31 +2,88 @@ use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
use crate::models::servarr_data::modals::EditIndexerModal;
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, EDIT_INDEXER_BLOCKS}; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, EDIT_INDEXER_BLOCKS};
use crate::models::servarr_models::EditIndexerParams;
use crate::network::radarr_network::RadarrEvent; use crate::network::radarr_network::RadarrEvent;
use crate::{handle_text_box_keys, handle_text_box_left_right_keys}; use crate::{handle_prompt_left_right_keys, handle_text_box_keys, handle_text_box_left_right_keys};
#[cfg(test)] #[cfg(test)]
#[path = "edit_indexer_handler_tests.rs"] #[path = "edit_indexer_handler_tests.rs"]
mod edit_indexer_handler_tests; mod edit_indexer_handler_tests;
pub(super) struct EditIndexerHandler<'a, 'b> { pub(super) struct EditIndexerHandler<'a, 'b> {
key: &'a Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
active_radarr_block: &'a ActiveRadarrBlock, active_radarr_block: ActiveRadarrBlock,
_context: &'a Option<ActiveRadarrBlock>, _context: Option<ActiveRadarrBlock>,
}
impl<'a, 'b> EditIndexerHandler<'a, 'b> {
fn build_edit_indexer_params(&mut self) -> EditIndexerParams {
let indexer_id = self.app.data.radarr_data.indexers.current_selection().id;
let tags = self
.app
.data
.radarr_data
.edit_indexer_modal
.as_ref()
.unwrap()
.tags
.text
.clone();
let params = {
let EditIndexerModal {
name,
enable_rss,
enable_automatic_search,
enable_interactive_search,
url,
api_key,
seed_ratio,
priority,
..
} = self
.app
.data
.radarr_data
.edit_indexer_modal
.as_ref()
.unwrap();
EditIndexerParams {
indexer_id,
name: Some(name.text.clone()),
enable_rss: Some(enable_rss.unwrap_or_default()),
enable_automatic_search: Some(enable_automatic_search.unwrap_or_default()),
enable_interactive_search: Some(enable_interactive_search.unwrap_or_default()),
url: Some(url.text.clone()),
api_key: Some(api_key.text.clone()),
seed_ratio: Some(seed_ratio.text.clone()),
tags: None,
tag_input_string: Some(tags),
priority: Some(*priority),
clear_tags: false,
}
};
self.app.data.radarr_data.edit_indexer_modal = None;
params
}
} }
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditIndexerHandler<'a, 'b> { impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditIndexerHandler<'a, 'b> {
fn accepts(active_block: &'a ActiveRadarrBlock) -> bool { fn accepts(active_block: ActiveRadarrBlock) -> bool {
EDIT_INDEXER_BLOCKS.contains(active_block) EDIT_INDEXER_BLOCKS.contains(&active_block)
} }
fn with( fn with(
key: &'a Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
active_block: &'a ActiveRadarrBlock, active_block: ActiveRadarrBlock,
_context: &'a Option<ActiveRadarrBlock>, _context: Option<ActiveRadarrBlock>,
) -> EditIndexerHandler<'a, 'b> { ) -> EditIndexerHandler<'a, 'b> {
EditIndexerHandler { EditIndexerHandler {
key, key,
@@ -36,7 +93,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditIndexerHandler<'
} }
} }
fn get_key(&self) -> &Key { fn get_key(&self) -> Key {
self.key self.key
} }
@@ -45,14 +102,42 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditIndexerHandler<'
} }
fn handle_scroll_up(&mut self) { fn handle_scroll_up(&mut self) {
if self.active_radarr_block == &ActiveRadarrBlock::EditIndexerPrompt { match self.active_radarr_block {
self.app.data.radarr_data.selected_block.previous(); ActiveRadarrBlock::EditIndexerPrompt => {
self.app.data.radarr_data.selected_block.up();
}
ActiveRadarrBlock::EditIndexerPriorityInput => {
self
.app
.data
.radarr_data
.edit_indexer_modal
.as_mut()
.unwrap()
.priority += 1;
}
_ => (),
} }
} }
fn handle_scroll_down(&mut self) { fn handle_scroll_down(&mut self) {
if self.active_radarr_block == &ActiveRadarrBlock::EditIndexerPrompt { match self.active_radarr_block {
self.app.data.radarr_data.selected_block.next(); ActiveRadarrBlock::EditIndexerPrompt => {
self.app.data.radarr_data.selected_block.down();
}
ActiveRadarrBlock::EditIndexerPriorityInput => {
let edit_indexer_modal = self
.app
.data
.radarr_data
.edit_indexer_modal
.as_mut()
.unwrap();
if edit_indexer_modal.priority > 0 {
edit_indexer_modal.priority -= 1;
}
}
_ => (),
} }
} }
@@ -183,15 +268,11 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditIndexerHandler<'
fn handle_left_right_action(&mut self) { fn handle_left_right_action(&mut self) {
match self.active_radarr_block { match self.active_radarr_block {
ActiveRadarrBlock::EditIndexerPrompt => { ActiveRadarrBlock::EditIndexerPrompt => {
if self.app.data.radarr_data.selected_block.get_active_block() handle_prompt_left_right_keys!(
== &ActiveRadarrBlock::EditIndexerConfirmPrompt self,
{ ActiveRadarrBlock::EditIndexerConfirmPrompt,
handle_prompt_toggle(self.app, self.key); radarr_data
} else { );
let len = self.app.data.radarr_data.selected_block.blocks.len();
let idx = self.app.data.radarr_data.selected_block.index;
self.app.data.radarr_data.selected_block.index = (idx + 5) % len;
}
} }
ActiveRadarrBlock::EditIndexerNameInput => { ActiveRadarrBlock::EditIndexerNameInput => {
handle_text_box_left_right_keys!( handle_text_box_left_right_keys!(
@@ -270,15 +351,15 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditIndexerHandler<'
fn handle_submit(&mut self) { fn handle_submit(&mut self) {
match self.active_radarr_block { match self.active_radarr_block {
ActiveRadarrBlock::EditIndexerPrompt => { ActiveRadarrBlock::EditIndexerPrompt => {
let selected_block = *self.app.data.radarr_data.selected_block.get_active_block(); let selected_block = self.app.data.radarr_data.selected_block.get_active_block();
match selected_block { match selected_block {
ActiveRadarrBlock::EditIndexerConfirmPrompt => { ActiveRadarrBlock::EditIndexerConfirmPrompt => {
let radarr_data = &mut self.app.data.radarr_data; if self.app.data.radarr_data.prompt_confirm {
if radarr_data.prompt_confirm { self.app.data.radarr_data.prompt_confirm_action =
radarr_data.prompt_confirm_action = Some(RadarrEvent::EditIndexer(None)); Some(RadarrEvent::EditIndexer(self.build_edit_indexer_params()));
self.app.should_refresh = true; self.app.should_refresh = true;
} else { } else {
radarr_data.edit_indexer_modal = None; self.app.data.radarr_data.edit_indexer_modal = None;
} }
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
@@ -291,6 +372,9 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditIndexerHandler<'
self.app.push_navigation_stack(selected_block.into()); self.app.push_navigation_stack(selected_block.into());
self.app.should_ignore_quit_key = true; self.app.should_ignore_quit_key = true;
} }
ActiveRadarrBlock::EditIndexerPriorityInput => self
.app
.push_navigation_stack(ActiveRadarrBlock::EditIndexerPriorityInput.into()),
ActiveRadarrBlock::EditIndexerToggleEnableRss => { ActiveRadarrBlock::EditIndexerToggleEnableRss => {
let indexer = self let indexer = self
.app .app
@@ -334,6 +418,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditIndexerHandler<'
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
self.app.should_ignore_quit_key = false; self.app.should_ignore_quit_key = false;
} }
ActiveRadarrBlock::EditIndexerPriorityInput => self.app.pop_navigation_stack(),
_ => (), _ => (),
} }
} }
@@ -349,6 +434,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditIndexerHandler<'
| ActiveRadarrBlock::EditIndexerUrlInput | ActiveRadarrBlock::EditIndexerUrlInput
| ActiveRadarrBlock::EditIndexerApiKeyInput | ActiveRadarrBlock::EditIndexerApiKeyInput
| ActiveRadarrBlock::EditIndexerSeedRatioInput | ActiveRadarrBlock::EditIndexerSeedRatioInput
| ActiveRadarrBlock::EditIndexerPriorityInput
| ActiveRadarrBlock::EditIndexerTagsInput => { | ActiveRadarrBlock::EditIndexerTagsInput => {
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
self.app.should_ignore_quit_key = false; self.app.should_ignore_quit_key = false;
@@ -431,11 +517,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditIndexerHandler<'
} }
ActiveRadarrBlock::EditIndexerPrompt => { ActiveRadarrBlock::EditIndexerPrompt => {
if self.app.data.radarr_data.selected_block.get_active_block() if self.app.data.radarr_data.selected_block.get_active_block()
== &ActiveRadarrBlock::EditIndexerConfirmPrompt == ActiveRadarrBlock::EditIndexerConfirmPrompt
&& *self.key == DEFAULT_KEYBINDINGS.confirm.key && self.key == DEFAULT_KEYBINDINGS.confirm.key
{ {
self.app.data.radarr_data.prompt_confirm = true; self.app.data.radarr_data.prompt_confirm = true;
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::EditIndexer(None)); self.app.data.radarr_data.prompt_confirm_action =
Some(RadarrEvent::EditIndexer(self.build_edit_indexer_params()));
self.app.should_refresh = true; self.app.should_refresh = true;
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
File diff suppressed because it is too large Load Diff
@@ -2,33 +2,42 @@ use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
use crate::models::radarr_models::IndexerSettings;
use crate::models::servarr_data::radarr::radarr_data::{ use crate::models::servarr_data::radarr::radarr_data::{
ActiveRadarrBlock, INDEXER_SETTINGS_BLOCKS, ActiveRadarrBlock, INDEXER_SETTINGS_BLOCKS,
}; };
use crate::network::radarr_network::RadarrEvent; use crate::network::radarr_network::RadarrEvent;
use crate::{handle_text_box_keys, handle_text_box_left_right_keys}; use crate::{handle_prompt_left_right_keys, handle_text_box_keys, handle_text_box_left_right_keys};
#[cfg(test)] #[cfg(test)]
#[path = "edit_indexer_settings_handler_tests.rs"] #[path = "edit_indexer_settings_handler_tests.rs"]
mod edit_indexer_settings_handler_tests; mod edit_indexer_settings_handler_tests;
pub(super) struct IndexerSettingsHandler<'a, 'b> { pub(super) struct IndexerSettingsHandler<'a, 'b> {
key: &'a Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
active_radarr_block: &'a ActiveRadarrBlock, active_radarr_block: ActiveRadarrBlock,
_context: &'a Option<ActiveRadarrBlock>, _context: Option<ActiveRadarrBlock>,
}
impl<'a, 'b> IndexerSettingsHandler<'a, 'b> {
fn build_edit_indexer_settings_body(&mut self) -> IndexerSettings {
let indexer_settings = self.app.data.radarr_data.indexer_settings.clone().unwrap();
self.app.data.radarr_data.indexer_settings = None;
indexer_settings
}
} }
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexerSettingsHandler<'a, 'b> { impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexerSettingsHandler<'a, 'b> {
fn accepts(active_block: &'a ActiveRadarrBlock) -> bool { fn accepts(active_block: ActiveRadarrBlock) -> bool {
INDEXER_SETTINGS_BLOCKS.contains(active_block) INDEXER_SETTINGS_BLOCKS.contains(&active_block)
} }
fn with( fn with(
key: &'a Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
active_block: &'a ActiveRadarrBlock, active_block: ActiveRadarrBlock,
_context: &'a Option<ActiveRadarrBlock>, _context: Option<ActiveRadarrBlock>,
) -> IndexerSettingsHandler<'a, 'b> { ) -> IndexerSettingsHandler<'a, 'b> {
IndexerSettingsHandler { IndexerSettingsHandler {
key, key,
@@ -38,7 +47,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexerSettingsHandl
} }
} }
fn get_key(&self) -> &Key { fn get_key(&self) -> Key {
self.key self.key
} }
@@ -50,7 +59,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexerSettingsHandl
let indexer_settings = self.app.data.radarr_data.indexer_settings.as_mut().unwrap(); let indexer_settings = self.app.data.radarr_data.indexer_settings.as_mut().unwrap();
match self.active_radarr_block { match self.active_radarr_block {
ActiveRadarrBlock::AllIndexerSettingsPrompt => { ActiveRadarrBlock::AllIndexerSettingsPrompt => {
self.app.data.radarr_data.selected_block.previous(); self.app.data.radarr_data.selected_block.up();
} }
ActiveRadarrBlock::IndexerSettingsMinimumAgeInput => { ActiveRadarrBlock::IndexerSettingsMinimumAgeInput => {
indexer_settings.minimum_age += 1; indexer_settings.minimum_age += 1;
@@ -75,7 +84,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexerSettingsHandl
let indexer_settings = self.app.data.radarr_data.indexer_settings.as_mut().unwrap(); let indexer_settings = self.app.data.radarr_data.indexer_settings.as_mut().unwrap();
match self.active_radarr_block { match self.active_radarr_block {
ActiveRadarrBlock::AllIndexerSettingsPrompt => { ActiveRadarrBlock::AllIndexerSettingsPrompt => {
self.app.data.radarr_data.selected_block.next() self.app.data.radarr_data.selected_block.down()
} }
ActiveRadarrBlock::IndexerSettingsMinimumAgeInput => { ActiveRadarrBlock::IndexerSettingsMinimumAgeInput => {
if indexer_settings.minimum_age > 0 { if indexer_settings.minimum_age > 0 {
@@ -105,7 +114,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexerSettingsHandl
} }
fn handle_home(&mut self) { fn handle_home(&mut self) {
if self.active_radarr_block == &ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput { if self.active_radarr_block == ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput {
self self
.app .app
.data .data
@@ -119,7 +128,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexerSettingsHandl
} }
fn handle_end(&mut self) { fn handle_end(&mut self) {
if self.active_radarr_block == &ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput { if self.active_radarr_block == ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput {
self self
.app .app
.data .data
@@ -137,15 +146,11 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexerSettingsHandl
fn handle_left_right_action(&mut self) { fn handle_left_right_action(&mut self) {
match self.active_radarr_block { match self.active_radarr_block {
ActiveRadarrBlock::AllIndexerSettingsPrompt => { ActiveRadarrBlock::AllIndexerSettingsPrompt => {
if self.app.data.radarr_data.selected_block.get_active_block() handle_prompt_left_right_keys!(
== &ActiveRadarrBlock::IndexerSettingsConfirmPrompt self,
{ ActiveRadarrBlock::IndexerSettingsConfirmPrompt,
handle_prompt_toggle(self.app, self.key); radarr_data
} else { );
let len = self.app.data.radarr_data.selected_block.blocks.len();
let idx = self.app.data.radarr_data.selected_block.index;
self.app.data.radarr_data.selected_block.index = (idx + 5) % len;
}
} }
ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput => { ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput => {
handle_text_box_left_right_keys!( handle_text_box_left_right_keys!(
@@ -170,12 +175,13 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexerSettingsHandl
ActiveRadarrBlock::AllIndexerSettingsPrompt => { ActiveRadarrBlock::AllIndexerSettingsPrompt => {
match self.app.data.radarr_data.selected_block.get_active_block() { match self.app.data.radarr_data.selected_block.get_active_block() {
ActiveRadarrBlock::IndexerSettingsConfirmPrompt => { ActiveRadarrBlock::IndexerSettingsConfirmPrompt => {
let radarr_data = &mut self.app.data.radarr_data; if self.app.data.radarr_data.prompt_confirm {
if radarr_data.prompt_confirm { self.app.data.radarr_data.prompt_confirm_action = Some(
radarr_data.prompt_confirm_action = Some(RadarrEvent::EditAllIndexerSettings(None)); RadarrEvent::EditAllIndexerSettings(self.build_edit_indexer_settings_body()),
);
self.app.should_refresh = true; self.app.should_refresh = true;
} else { } else {
radarr_data.indexer_settings = None; self.app.data.radarr_data.indexer_settings = None;
} }
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
@@ -187,7 +193,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexerSettingsHandl
| ActiveRadarrBlock::IndexerSettingsRssSyncIntervalInput => { | ActiveRadarrBlock::IndexerSettingsRssSyncIntervalInput => {
self.app.push_navigation_stack( self.app.push_navigation_stack(
( (
*self.app.data.radarr_data.selected_block.get_active_block(), self.app.data.radarr_data.selected_block.get_active_block(),
None, None,
) )
.into(), .into(),
@@ -258,12 +264,13 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexerSettingsHandl
} }
ActiveRadarrBlock::AllIndexerSettingsPrompt => { ActiveRadarrBlock::AllIndexerSettingsPrompt => {
if self.app.data.radarr_data.selected_block.get_active_block() if self.app.data.radarr_data.selected_block.get_active_block()
== &ActiveRadarrBlock::IndexerSettingsConfirmPrompt == ActiveRadarrBlock::IndexerSettingsConfirmPrompt
&& *self.key == DEFAULT_KEYBINDINGS.confirm.key && self.key == DEFAULT_KEYBINDINGS.confirm.key
{ {
self.app.data.radarr_data.prompt_confirm = true; self.app.data.radarr_data.prompt_confirm = true;
self.app.data.radarr_data.prompt_confirm_action = self.app.data.radarr_data.prompt_confirm_action = Some(
Some(RadarrEvent::EditAllIndexerSettings(None)); RadarrEvent::EditAllIndexerSettings(self.build_edit_indexer_settings_body()),
);
self.app.should_refresh = true; self.app.should_refresh = true;
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
@@ -1,11 +1,13 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use pretty_assertions::assert_eq;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::radarr_handlers::indexers::edit_indexer_settings_handler::IndexerSettingsHandler; use crate::handlers::radarr_handlers::indexers::edit_indexer_settings_handler::IndexerSettingsHandler;
use crate::handlers::radarr_handlers::radarr_handler_test_utils::utils::indexer_settings;
use crate::handlers::KeyEventHandler; use crate::handlers::KeyEventHandler;
use crate::models::radarr_models::IndexerSettings; use crate::models::radarr_models::IndexerSettings;
use crate::models::servarr_data::radarr::radarr_data::{ use crate::models::servarr_data::radarr::radarr_data::{
@@ -27,7 +29,7 @@ mod tests {
let mut app = App::default(); let mut app = App::default();
app.data.radarr_data.indexer_settings = Some(IndexerSettings::default()); app.data.radarr_data.indexer_settings = Some(IndexerSettings::default());
IndexerSettingsHandler::with(&$key, &mut app, &$block, &None).handle(); IndexerSettingsHandler::with($key, &mut app, $block, None).handle();
if $key == Key::Up { if $key == Key::Up {
assert_eq!( assert_eq!(
@@ -64,7 +66,7 @@ mod tests {
0 0
); );
IndexerSettingsHandler::with(&Key::Up, &mut app, &$block, &None).handle(); IndexerSettingsHandler::with(Key::Up, &mut app, $block, None).handle();
assert_eq!( assert_eq!(
app app
@@ -77,7 +79,7 @@ mod tests {
1 1
); );
IndexerSettingsHandler::with(&$key, &mut app, &$block, &None).handle(); IndexerSettingsHandler::with($key, &mut app, $block, None).handle();
assert_eq!( assert_eq!(
app app
.data .data
@@ -98,26 +100,26 @@ mod tests {
let mut app = App::default(); let mut app = App::default();
app.data.radarr_data.indexer_settings = Some(IndexerSettings::default()); app.data.radarr_data.indexer_settings = Some(IndexerSettings::default());
app.data.radarr_data.selected_block = app.data.radarr_data.selected_block =
BlockSelectionState::new(&INDEXER_SETTINGS_SELECTION_BLOCKS); BlockSelectionState::new(INDEXER_SETTINGS_SELECTION_BLOCKS);
app.data.radarr_data.selected_block.next(); app.data.radarr_data.selected_block.down();
IndexerSettingsHandler::with( IndexerSettingsHandler::with(
&key, key,
&mut app, &mut app,
&ActiveRadarrBlock::AllIndexerSettingsPrompt, ActiveRadarrBlock::AllIndexerSettingsPrompt,
&None, None,
) )
.handle(); .handle();
if key == Key::Up { if key == Key::Up {
assert_eq!( assert_eq!(
app.data.radarr_data.selected_block.get_active_block(), app.data.radarr_data.selected_block.get_active_block(),
&ActiveRadarrBlock::IndexerSettingsMinimumAgeInput ActiveRadarrBlock::IndexerSettingsMinimumAgeInput
); );
} else { } else {
assert_eq!( assert_eq!(
app.data.radarr_data.selected_block.get_active_block(), app.data.radarr_data.selected_block.get_active_block(),
&ActiveRadarrBlock::IndexerSettingsMaximumSizeInput ActiveRadarrBlock::IndexerSettingsMaximumSizeInput
); );
} }
} }
@@ -130,20 +132,20 @@ mod tests {
app.is_loading = true; app.is_loading = true;
app.data.radarr_data.indexer_settings = Some(IndexerSettings::default()); app.data.radarr_data.indexer_settings = Some(IndexerSettings::default());
app.data.radarr_data.selected_block = app.data.radarr_data.selected_block =
BlockSelectionState::new(&INDEXER_SETTINGS_SELECTION_BLOCKS); BlockSelectionState::new(INDEXER_SETTINGS_SELECTION_BLOCKS);
app.data.radarr_data.selected_block.next(); app.data.radarr_data.selected_block.down();
IndexerSettingsHandler::with( IndexerSettingsHandler::with(
&key, key,
&mut app, &mut app,
&ActiveRadarrBlock::AllIndexerSettingsPrompt, ActiveRadarrBlock::AllIndexerSettingsPrompt,
&None, None,
) )
.handle(); .handle();
assert_eq!( assert_eq!(
app.data.radarr_data.selected_block.get_active_block(), app.data.radarr_data.selected_block.get_active_block(),
&ActiveRadarrBlock::IndexerSettingsRetentionInput ActiveRadarrBlock::IndexerSettingsRetentionInput
); );
} }
@@ -218,10 +220,10 @@ mod tests {
}); });
IndexerSettingsHandler::with( IndexerSettingsHandler::with(
&DEFAULT_KEYBINDINGS.home.key, DEFAULT_KEYBINDINGS.home.key,
&mut app, &mut app,
&ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput, ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput,
&None, None,
) )
.handle(); .handle();
@@ -239,10 +241,10 @@ mod tests {
); );
IndexerSettingsHandler::with( IndexerSettingsHandler::with(
&DEFAULT_KEYBINDINGS.end.key, DEFAULT_KEYBINDINGS.end.key,
&mut app, &mut app,
&ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput, ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput,
&None, None,
) )
.handle(); .handle();
@@ -276,24 +278,24 @@ mod tests {
fn test_left_right_prompt_toggle(#[values(Key::Left, Key::Right)] key: Key) { fn test_left_right_prompt_toggle(#[values(Key::Left, Key::Right)] key: Key) {
let mut app = App::default(); let mut app = App::default();
app.data.radarr_data.selected_block = app.data.radarr_data.selected_block =
BlockSelectionState::new(&INDEXER_SETTINGS_SELECTION_BLOCKS); BlockSelectionState::new(INDEXER_SETTINGS_SELECTION_BLOCKS);
app.data.radarr_data.selected_block.index = INDEXER_SETTINGS_SELECTION_BLOCKS.len() - 1; app.data.radarr_data.selected_block.y = INDEXER_SETTINGS_SELECTION_BLOCKS.len() - 1;
IndexerSettingsHandler::with( IndexerSettingsHandler::with(
&key, key,
&mut app, &mut app,
&ActiveRadarrBlock::AllIndexerSettingsPrompt, ActiveRadarrBlock::AllIndexerSettingsPrompt,
&None, None,
) )
.handle(); .handle();
assert!(app.data.radarr_data.prompt_confirm); assert!(app.data.radarr_data.prompt_confirm);
IndexerSettingsHandler::with( IndexerSettingsHandler::with(
&key, key,
&mut app, &mut app,
&ActiveRadarrBlock::AllIndexerSettingsPrompt, ActiveRadarrBlock::AllIndexerSettingsPrompt,
&None, None,
) )
.handle(); .handle();
@@ -323,44 +325,44 @@ mod tests {
)] )]
fn test_left_right_block_toggle( fn test_left_right_block_toggle(
#[values(Key::Left, Key::Right)] key: Key, #[values(Key::Left, Key::Right)] key: Key,
#[case] starting_index: usize, #[case] starting_y_index: usize,
#[case] left_block: ActiveRadarrBlock, #[case] left_block: ActiveRadarrBlock,
#[case] right_block: ActiveRadarrBlock, #[case] right_block: ActiveRadarrBlock,
) { ) {
let mut app = App::default(); let mut app = App::default();
app.data.radarr_data.selected_block = app.data.radarr_data.selected_block =
BlockSelectionState::new(&INDEXER_SETTINGS_SELECTION_BLOCKS); BlockSelectionState::new(INDEXER_SETTINGS_SELECTION_BLOCKS);
app.data.radarr_data.selected_block.index = starting_index; app.data.radarr_data.selected_block.y = starting_y_index;
assert_eq!( assert_eq!(
app.data.radarr_data.selected_block.get_active_block(), app.data.radarr_data.selected_block.get_active_block(),
&left_block left_block
); );
IndexerSettingsHandler::with( IndexerSettingsHandler::with(
&key, key,
&mut app, &mut app,
&ActiveRadarrBlock::AllIndexerSettingsPrompt, ActiveRadarrBlock::AllIndexerSettingsPrompt,
&None, None,
) )
.handle(); .handle();
assert_eq!( assert_eq!(
app.data.radarr_data.selected_block.get_active_block(), app.data.radarr_data.selected_block.get_active_block(),
&right_block right_block
); );
IndexerSettingsHandler::with( IndexerSettingsHandler::with(
&key, key,
&mut app, &mut app,
&ActiveRadarrBlock::AllIndexerSettingsPrompt, ActiveRadarrBlock::AllIndexerSettingsPrompt,
&None, None,
) )
.handle(); .handle();
assert_eq!( assert_eq!(
app.data.radarr_data.selected_block.get_active_block(), app.data.radarr_data.selected_block.get_active_block(),
&left_block left_block
); );
} }
@@ -373,10 +375,10 @@ mod tests {
}); });
IndexerSettingsHandler::with( IndexerSettingsHandler::with(
&DEFAULT_KEYBINDINGS.left.key, DEFAULT_KEYBINDINGS.left.key,
&mut app, &mut app,
&ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput, ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput,
&None, None,
) )
.handle(); .handle();
@@ -394,10 +396,10 @@ mod tests {
); );
IndexerSettingsHandler::with( IndexerSettingsHandler::with(
&DEFAULT_KEYBINDINGS.right.key, DEFAULT_KEYBINDINGS.right.key,
&mut app, &mut app,
&ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput, ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput,
&None, None,
) )
.handle(); .handle();
@@ -438,23 +440,23 @@ mod tests {
app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.push_navigation_stack(ActiveRadarrBlock::Indexers.into());
app.push_navigation_stack(ActiveRadarrBlock::AllIndexerSettingsPrompt.into()); app.push_navigation_stack(ActiveRadarrBlock::AllIndexerSettingsPrompt.into());
app.data.radarr_data.selected_block = app.data.radarr_data.selected_block =
BlockSelectionState::new(&INDEXER_SETTINGS_SELECTION_BLOCKS); BlockSelectionState::new(INDEXER_SETTINGS_SELECTION_BLOCKS);
app app
.data .data
.radarr_data .radarr_data
.selected_block .selected_block
.set_index(INDEXER_SETTINGS_SELECTION_BLOCKS.len() - 1); .set_index(0, INDEXER_SETTINGS_SELECTION_BLOCKS.len() - 1);
app.data.radarr_data.indexer_settings = Some(IndexerSettings::default()); app.data.radarr_data.indexer_settings = Some(IndexerSettings::default());
IndexerSettingsHandler::with( IndexerSettingsHandler::with(
&SUBMIT_KEY, SUBMIT_KEY,
&mut app, &mut app,
&ActiveRadarrBlock::AllIndexerSettingsPrompt, ActiveRadarrBlock::AllIndexerSettingsPrompt,
&None, None,
) )
.handle(); .handle();
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Indexers.into()); assert_eq!(app.get_current_route(), ActiveRadarrBlock::Indexers.into());
assert_eq!(app.data.radarr_data.prompt_confirm_action, None); assert_eq!(app.data.radarr_data.prompt_confirm_action, None);
assert!(!app.should_refresh); assert!(!app.should_refresh);
assert_eq!(app.data.radarr_data.indexer_settings, None); assert_eq!(app.data.radarr_data.indexer_settings, None);
@@ -466,29 +468,29 @@ mod tests {
app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.push_navigation_stack(ActiveRadarrBlock::Indexers.into());
app.push_navigation_stack(ActiveRadarrBlock::AllIndexerSettingsPrompt.into()); app.push_navigation_stack(ActiveRadarrBlock::AllIndexerSettingsPrompt.into());
app.data.radarr_data.selected_block = app.data.radarr_data.selected_block =
BlockSelectionState::new(&INDEXER_SETTINGS_SELECTION_BLOCKS); BlockSelectionState::new(INDEXER_SETTINGS_SELECTION_BLOCKS);
app app
.data .data
.radarr_data .radarr_data
.selected_block .selected_block
.set_index(INDEXER_SETTINGS_SELECTION_BLOCKS.len() - 1); .set_index(0, INDEXER_SETTINGS_SELECTION_BLOCKS.len() - 1);
app.data.radarr_data.indexer_settings = Some(IndexerSettings::default()); app.data.radarr_data.indexer_settings = Some(indexer_settings());
app.data.radarr_data.prompt_confirm = true; app.data.radarr_data.prompt_confirm = true;
IndexerSettingsHandler::with( IndexerSettingsHandler::with(
&SUBMIT_KEY, SUBMIT_KEY,
&mut app, &mut app,
&ActiveRadarrBlock::AllIndexerSettingsPrompt, ActiveRadarrBlock::AllIndexerSettingsPrompt,
&None, None,
) )
.handle(); .handle();
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Indexers.into()); assert_eq!(app.get_current_route(), ActiveRadarrBlock::Indexers.into());
assert_eq!( assert_eq!(
app.data.radarr_data.prompt_confirm_action, app.data.radarr_data.prompt_confirm_action,
Some(RadarrEvent::EditAllIndexerSettings(None)) Some(RadarrEvent::EditAllIndexerSettings(indexer_settings()))
); );
assert!(app.data.radarr_data.indexer_settings.is_some()); assert!(app.data.radarr_data.indexer_settings.is_none());
assert!(app.should_refresh); assert!(app.should_refresh);
} }
@@ -502,71 +504,80 @@ mod tests {
app.data.radarr_data.prompt_confirm = true; app.data.radarr_data.prompt_confirm = true;
IndexerSettingsHandler::with( IndexerSettingsHandler::with(
&SUBMIT_KEY, SUBMIT_KEY,
&mut app, &mut app,
&ActiveRadarrBlock::AllIndexerSettingsPrompt, ActiveRadarrBlock::AllIndexerSettingsPrompt,
&None, None,
) )
.handle(); .handle();
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
&ActiveRadarrBlock::AllIndexerSettingsPrompt.into() ActiveRadarrBlock::AllIndexerSettingsPrompt.into()
); );
assert!(!app.should_refresh); assert!(!app.should_refresh);
} }
#[rstest] #[rstest]
#[case(ActiveRadarrBlock::IndexerSettingsMinimumAgeInput, 0)] #[case(ActiveRadarrBlock::IndexerSettingsMinimumAgeInput, 0, 0)]
#[case(ActiveRadarrBlock::IndexerSettingsRetentionInput, 1)] #[case(ActiveRadarrBlock::IndexerSettingsRetentionInput, 1, 0)]
#[case(ActiveRadarrBlock::IndexerSettingsMaximumSizeInput, 2)] #[case(ActiveRadarrBlock::IndexerSettingsMaximumSizeInput, 2, 0)]
#[case(ActiveRadarrBlock::IndexerSettingsAvailabilityDelayInput, 5)] #[case(ActiveRadarrBlock::IndexerSettingsAvailabilityDelayInput, 0, 1)]
#[case(ActiveRadarrBlock::IndexerSettingsRssSyncIntervalInput, 6)] #[case(ActiveRadarrBlock::IndexerSettingsRssSyncIntervalInput, 1, 1)]
fn test_edit_indexer_settings_prompt_submit_selected_block( fn test_edit_indexer_settings_prompt_submit_selected_block(
#[case] selected_block: ActiveRadarrBlock, #[case] selected_block: ActiveRadarrBlock,
#[case] index: usize, #[case] y_index: usize,
#[case] x_index: usize,
) { ) {
let mut app = App::default(); let mut app = App::default();
app.data.radarr_data.indexer_settings = Some(IndexerSettings::default()); app.data.radarr_data.indexer_settings = Some(IndexerSettings::default());
app.push_navigation_stack(ActiveRadarrBlock::AllIndexerSettingsPrompt.into()); app.push_navigation_stack(ActiveRadarrBlock::AllIndexerSettingsPrompt.into());
app.data.radarr_data.selected_block = app.data.radarr_data.selected_block =
BlockSelectionState::new(&INDEXER_SETTINGS_SELECTION_BLOCKS); BlockSelectionState::new(INDEXER_SETTINGS_SELECTION_BLOCKS);
app.data.radarr_data.selected_block.set_index(index); app
.data
.radarr_data
.selected_block
.set_index(x_index, y_index);
IndexerSettingsHandler::with( IndexerSettingsHandler::with(
&SUBMIT_KEY, SUBMIT_KEY,
&mut app, &mut app,
&ActiveRadarrBlock::AllIndexerSettingsPrompt, ActiveRadarrBlock::AllIndexerSettingsPrompt,
&None, None,
) )
.handle(); .handle();
assert_eq!(app.get_current_route(), &selected_block.into()); assert_eq!(app.get_current_route(), selected_block.into());
} }
#[rstest] #[rstest]
fn test_edit_indexer_settings_prompt_submit_selected_block_no_op_when_not_ready( fn test_edit_indexer_settings_prompt_submit_selected_block_no_op_when_not_ready(
#[values(0, 1, 2, 5, 6)] index: usize, #[values((0, 0), (1, 0), (2, 0), (0, 1), (1, 1))] index: (usize, usize),
) { ) {
let mut app = App::default(); let mut app = App::default();
app.is_loading = true; app.is_loading = true;
app.data.radarr_data.indexer_settings = Some(IndexerSettings::default()); app.data.radarr_data.indexer_settings = Some(IndexerSettings::default());
app.push_navigation_stack(ActiveRadarrBlock::AllIndexerSettingsPrompt.into()); app.push_navigation_stack(ActiveRadarrBlock::AllIndexerSettingsPrompt.into());
app.data.radarr_data.selected_block = app.data.radarr_data.selected_block =
BlockSelectionState::new(&INDEXER_SETTINGS_SELECTION_BLOCKS); BlockSelectionState::new(INDEXER_SETTINGS_SELECTION_BLOCKS);
app.data.radarr_data.selected_block.set_index(index); app
.data
.radarr_data
.selected_block
.set_index(index.1, index.0);
IndexerSettingsHandler::with( IndexerSettingsHandler::with(
&SUBMIT_KEY, SUBMIT_KEY,
&mut app, &mut app,
&ActiveRadarrBlock::AllIndexerSettingsPrompt, ActiveRadarrBlock::AllIndexerSettingsPrompt,
&None, None,
) )
.handle(); .handle();
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
&ActiveRadarrBlock::AllIndexerSettingsPrompt.into() ActiveRadarrBlock::AllIndexerSettingsPrompt.into()
); );
} }
@@ -576,20 +587,20 @@ mod tests {
app.data.radarr_data.indexer_settings = Some(IndexerSettings::default()); app.data.radarr_data.indexer_settings = Some(IndexerSettings::default());
app.push_navigation_stack(ActiveRadarrBlock::AllIndexerSettingsPrompt.into()); app.push_navigation_stack(ActiveRadarrBlock::AllIndexerSettingsPrompt.into());
app.data.radarr_data.selected_block = app.data.radarr_data.selected_block =
BlockSelectionState::new(&INDEXER_SETTINGS_SELECTION_BLOCKS); BlockSelectionState::new(INDEXER_SETTINGS_SELECTION_BLOCKS);
app.data.radarr_data.selected_block.set_index(7); app.data.radarr_data.selected_block.set_index(1, 2);
IndexerSettingsHandler::with( IndexerSettingsHandler::with(
&SUBMIT_KEY, SUBMIT_KEY,
&mut app, &mut app,
&ActiveRadarrBlock::AllIndexerSettingsPrompt, ActiveRadarrBlock::AllIndexerSettingsPrompt,
&None, None,
) )
.handle(); .handle();
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
&ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput.into() ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput.into()
); );
assert!(app.should_ignore_quit_key); assert!(app.should_ignore_quit_key);
} }
@@ -599,21 +610,21 @@ mod tests {
let mut app = App::default(); let mut app = App::default();
app.data.radarr_data.indexer_settings = Some(IndexerSettings::default()); app.data.radarr_data.indexer_settings = Some(IndexerSettings::default());
app.data.radarr_data.selected_block = app.data.radarr_data.selected_block =
BlockSelectionState::new(&INDEXER_SETTINGS_SELECTION_BLOCKS); BlockSelectionState::new(INDEXER_SETTINGS_SELECTION_BLOCKS);
app.data.radarr_data.selected_block.set_index(3); app.data.radarr_data.selected_block.set_index(0, 3);
app.push_navigation_stack(ActiveRadarrBlock::AllIndexerSettingsPrompt.into()); app.push_navigation_stack(ActiveRadarrBlock::AllIndexerSettingsPrompt.into());
IndexerSettingsHandler::with( IndexerSettingsHandler::with(
&SUBMIT_KEY, SUBMIT_KEY,
&mut app, &mut app,
&ActiveRadarrBlock::AllIndexerSettingsPrompt, ActiveRadarrBlock::AllIndexerSettingsPrompt,
&None, None,
) )
.handle(); .handle();
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
&ActiveRadarrBlock::AllIndexerSettingsPrompt.into() ActiveRadarrBlock::AllIndexerSettingsPrompt.into()
); );
assert!( assert!(
app app
@@ -626,16 +637,16 @@ mod tests {
); );
IndexerSettingsHandler::with( IndexerSettingsHandler::with(
&SUBMIT_KEY, SUBMIT_KEY,
&mut app, &mut app,
&ActiveRadarrBlock::AllIndexerSettingsPrompt, ActiveRadarrBlock::AllIndexerSettingsPrompt,
&None, None,
) )
.handle(); .handle();
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
&ActiveRadarrBlock::AllIndexerSettingsPrompt.into() ActiveRadarrBlock::AllIndexerSettingsPrompt.into()
); );
assert!( assert!(
!app !app
@@ -653,21 +664,21 @@ mod tests {
let mut app = App::default(); let mut app = App::default();
app.data.radarr_data.indexer_settings = Some(IndexerSettings::default()); app.data.radarr_data.indexer_settings = Some(IndexerSettings::default());
app.data.radarr_data.selected_block = app.data.radarr_data.selected_block =
BlockSelectionState::new(&INDEXER_SETTINGS_SELECTION_BLOCKS); BlockSelectionState::new(INDEXER_SETTINGS_SELECTION_BLOCKS);
app.data.radarr_data.selected_block.set_index(8); app.data.radarr_data.selected_block.set_index(1, 3);
app.push_navigation_stack(ActiveRadarrBlock::AllIndexerSettingsPrompt.into()); app.push_navigation_stack(ActiveRadarrBlock::AllIndexerSettingsPrompt.into());
IndexerSettingsHandler::with( IndexerSettingsHandler::with(
&SUBMIT_KEY, SUBMIT_KEY,
&mut app, &mut app,
&ActiveRadarrBlock::AllIndexerSettingsPrompt, ActiveRadarrBlock::AllIndexerSettingsPrompt,
&None, None,
) )
.handle(); .handle();
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
&ActiveRadarrBlock::AllIndexerSettingsPrompt.into() ActiveRadarrBlock::AllIndexerSettingsPrompt.into()
); );
assert!( assert!(
app app
@@ -680,16 +691,16 @@ mod tests {
); );
IndexerSettingsHandler::with( IndexerSettingsHandler::with(
&SUBMIT_KEY, SUBMIT_KEY,
&mut app, &mut app,
&ActiveRadarrBlock::AllIndexerSettingsPrompt, ActiveRadarrBlock::AllIndexerSettingsPrompt,
&None, None,
) )
.handle(); .handle();
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
&ActiveRadarrBlock::AllIndexerSettingsPrompt.into() ActiveRadarrBlock::AllIndexerSettingsPrompt.into()
); );
assert!( assert!(
!app !app
@@ -716,10 +727,10 @@ mod tests {
); );
IndexerSettingsHandler::with( IndexerSettingsHandler::with(
&SUBMIT_KEY, SUBMIT_KEY,
&mut app, &mut app,
&ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput, ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput,
&None, None,
) )
.handle(); .handle();
@@ -735,7 +746,7 @@ mod tests {
.is_empty()); .is_empty());
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
&ActiveRadarrBlock::AllIndexerSettingsPrompt.into() ActiveRadarrBlock::AllIndexerSettingsPrompt.into()
); );
} }
@@ -755,11 +766,11 @@ mod tests {
app.push_navigation_stack(ActiveRadarrBlock::AllIndexerSettingsPrompt.into()); app.push_navigation_stack(ActiveRadarrBlock::AllIndexerSettingsPrompt.into());
app.push_navigation_stack(active_radarr_block.into()); app.push_navigation_stack(active_radarr_block.into());
IndexerSettingsHandler::with(&SUBMIT_KEY, &mut app, &active_radarr_block, &None).handle(); IndexerSettingsHandler::with(SUBMIT_KEY, &mut app, active_radarr_block, None).handle();
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
&ActiveRadarrBlock::AllIndexerSettingsPrompt.into() ActiveRadarrBlock::AllIndexerSettingsPrompt.into()
); );
} }
} }
@@ -783,14 +794,14 @@ mod tests {
app.data.radarr_data.indexer_settings = Some(IndexerSettings::default()); app.data.radarr_data.indexer_settings = Some(IndexerSettings::default());
IndexerSettingsHandler::with( IndexerSettingsHandler::with(
&ESC_KEY, ESC_KEY,
&mut app, &mut app,
&ActiveRadarrBlock::AllIndexerSettingsPrompt, ActiveRadarrBlock::AllIndexerSettingsPrompt,
&None, None,
) )
.handle(); .handle();
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Indexers.into()); assert_eq!(app.get_current_route(), ActiveRadarrBlock::Indexers.into());
assert!(!app.data.radarr_data.prompt_confirm); assert!(!app.data.radarr_data.prompt_confirm);
assert_eq!(app.data.radarr_data.indexer_settings, None); assert_eq!(app.data.radarr_data.indexer_settings, None);
} }
@@ -806,14 +817,14 @@ mod tests {
app.should_ignore_quit_key = true; app.should_ignore_quit_key = true;
IndexerSettingsHandler::with( IndexerSettingsHandler::with(
&ESC_KEY, ESC_KEY,
&mut app, &mut app,
&ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput, ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput,
&None, None,
) )
.handle(); .handle();
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Indexers.into()); assert_eq!(app.get_current_route(), ActiveRadarrBlock::Indexers.into());
assert!(!app.should_ignore_quit_key); assert!(!app.should_ignore_quit_key);
assert_eq!( assert_eq!(
app.data.radarr_data.indexer_settings, app.data.radarr_data.indexer_settings,
@@ -838,9 +849,9 @@ mod tests {
app.push_navigation_stack(active_radarr_block.into()); app.push_navigation_stack(active_radarr_block.into());
app.data.radarr_data.indexer_settings = Some(IndexerSettings::default()); app.data.radarr_data.indexer_settings = Some(IndexerSettings::default());
IndexerSettingsHandler::with(&ESC_KEY, &mut app, &active_radarr_block, &None).handle(); IndexerSettingsHandler::with(ESC_KEY, &mut app, active_radarr_block, None).handle();
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Indexers.into()); assert_eq!(app.get_current_route(), ActiveRadarrBlock::Indexers.into());
assert_eq!( assert_eq!(
app.data.radarr_data.indexer_settings, app.data.radarr_data.indexer_settings,
Some(IndexerSettings::default()) Some(IndexerSettings::default())
@@ -849,7 +860,7 @@ mod tests {
} }
mod test_handle_key_char { mod test_handle_key_char {
use pretty_assertions::assert_str_eq; use pretty_assertions::{assert_eq, assert_str_eq};
use crate::{ use crate::{
models::{ models::{
@@ -870,10 +881,10 @@ mod tests {
}); });
IndexerSettingsHandler::with( IndexerSettingsHandler::with(
&DEFAULT_KEYBINDINGS.backspace.key, DEFAULT_KEYBINDINGS.backspace.key,
&mut app, &mut app,
&ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput, ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput,
&None, None,
) )
.handle(); .handle();
@@ -896,10 +907,10 @@ mod tests {
app.data.radarr_data.indexer_settings = Some(IndexerSettings::default()); app.data.radarr_data.indexer_settings = Some(IndexerSettings::default());
IndexerSettingsHandler::with( IndexerSettingsHandler::with(
&Key::Char('h'), Key::Char('h'),
&mut app, &mut app,
&ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput, ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput,
&None, None,
) )
.handle(); .handle();
@@ -922,28 +933,28 @@ mod tests {
app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.push_navigation_stack(ActiveRadarrBlock::Indexers.into());
app.push_navigation_stack(ActiveRadarrBlock::AllIndexerSettingsPrompt.into()); app.push_navigation_stack(ActiveRadarrBlock::AllIndexerSettingsPrompt.into());
app.data.radarr_data.selected_block = app.data.radarr_data.selected_block =
BlockSelectionState::new(&INDEXER_SETTINGS_SELECTION_BLOCKS); BlockSelectionState::new(INDEXER_SETTINGS_SELECTION_BLOCKS);
app app
.data .data
.radarr_data .radarr_data
.selected_block .selected_block
.set_index(INDEXER_SETTINGS_SELECTION_BLOCKS.len() - 1); .set_index(0, INDEXER_SETTINGS_SELECTION_BLOCKS.len() - 1);
app.data.radarr_data.indexer_settings = Some(IndexerSettings::default()); app.data.radarr_data.indexer_settings = Some(indexer_settings());
IndexerSettingsHandler::with( IndexerSettingsHandler::with(
&DEFAULT_KEYBINDINGS.confirm.key, DEFAULT_KEYBINDINGS.confirm.key,
&mut app, &mut app,
&ActiveRadarrBlock::AllIndexerSettingsPrompt, ActiveRadarrBlock::AllIndexerSettingsPrompt,
&None, None,
) )
.handle(); .handle();
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Indexers.into()); assert_eq!(app.get_current_route(), ActiveRadarrBlock::Indexers.into());
assert_eq!( assert_eq!(
app.data.radarr_data.prompt_confirm_action, app.data.radarr_data.prompt_confirm_action,
Some(RadarrEvent::EditAllIndexerSettings(None)) Some(RadarrEvent::EditAllIndexerSettings(indexer_settings()))
); );
assert!(app.data.radarr_data.indexer_settings.is_some()); assert!(app.data.radarr_data.indexer_settings.is_none());
assert!(app.should_refresh); assert!(app.should_refresh);
} }
} }
@@ -952,23 +963,40 @@ mod tests {
fn test_indexer_settings_handler_accepts() { fn test_indexer_settings_handler_accepts() {
ActiveRadarrBlock::iter().for_each(|active_radarr_block| { ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
if INDEXER_SETTINGS_BLOCKS.contains(&active_radarr_block) { if INDEXER_SETTINGS_BLOCKS.contains(&active_radarr_block) {
assert!(IndexerSettingsHandler::accepts(&active_radarr_block)); assert!(IndexerSettingsHandler::accepts(active_radarr_block));
} else { } else {
assert!(!IndexerSettingsHandler::accepts(&active_radarr_block)); assert!(!IndexerSettingsHandler::accepts(active_radarr_block));
} }
}) })
} }
#[test]
fn test_build_edit_indexer_settings_body() {
let mut app = App::default();
app.data.radarr_data.indexer_settings = Some(indexer_settings());
let body = IndexerSettingsHandler::with(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveRadarrBlock::AllIndexerSettingsPrompt,
None,
)
.build_edit_indexer_settings_body();
assert_eq!(body, indexer_settings());
assert!(app.data.radarr_data.indexer_settings.is_none());
}
#[test] #[test]
fn test_edit_indexer_settings_handler_not_ready_when_loading() { fn test_edit_indexer_settings_handler_not_ready_when_loading() {
let mut app = App::default(); let mut app = App::default();
app.is_loading = true; app.is_loading = true;
let handler = IndexerSettingsHandler::with( let handler = IndexerSettingsHandler::with(
&DEFAULT_KEYBINDINGS.esc.key, DEFAULT_KEYBINDINGS.esc.key,
&mut app, &mut app,
&ActiveRadarrBlock::AllIndexerSettingsPrompt, ActiveRadarrBlock::AllIndexerSettingsPrompt,
&None, None,
); );
assert!(!handler.is_ready()); assert!(!handler.is_ready());
@@ -980,10 +1008,10 @@ mod tests {
app.is_loading = false; app.is_loading = false;
let handler = IndexerSettingsHandler::with( let handler = IndexerSettingsHandler::with(
&DEFAULT_KEYBINDINGS.esc.key, DEFAULT_KEYBINDINGS.esc.key,
&mut app, &mut app,
&ActiveRadarrBlock::AllIndexerSettingsPrompt, ActiveRadarrBlock::AllIndexerSettingsPrompt,
&None, None,
); );
assert!(!handler.is_ready()); assert!(!handler.is_ready());
@@ -996,10 +1024,10 @@ mod tests {
app.data.radarr_data.indexer_settings = Some(IndexerSettings::default()); app.data.radarr_data.indexer_settings = Some(IndexerSettings::default());
let handler = IndexerSettingsHandler::with( let handler = IndexerSettingsHandler::with(
&DEFAULT_KEYBINDINGS.esc.key, DEFAULT_KEYBINDINGS.esc.key,
&mut app, &mut app,
&ActiveRadarrBlock::AllIndexerSettingsPrompt, ActiveRadarrBlock::AllIndexerSettingsPrompt,
&None, None,
); );
assert!(handler.is_ready()); assert!(handler.is_ready());
@@ -1,6 +1,6 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use pretty_assertions::{assert_eq, assert_str_eq}; use pretty_assertions::assert_eq;
use rstest::rstest; use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
@@ -8,6 +8,7 @@ mod tests {
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::radarr_handlers::indexers::IndexersHandler; use crate::handlers::radarr_handlers::indexers::IndexersHandler;
use crate::handlers::radarr_handlers::radarr_handler_test_utils::utils::indexer;
use crate::handlers::KeyEventHandler; use crate::handlers::KeyEventHandler;
use crate::models::servarr_data::radarr::radarr_data::{ use crate::models::servarr_data::radarr::radarr_data::{
ActiveRadarrBlock, EDIT_INDEXER_BLOCKS, INDEXERS_BLOCKS, INDEXER_SETTINGS_BLOCKS, ActiveRadarrBlock, EDIT_INDEXER_BLOCKS, INDEXERS_BLOCKS, INDEXER_SETTINGS_BLOCKS,
@@ -15,107 +16,6 @@ mod tests {
use crate::models::servarr_models::Indexer; use crate::models::servarr_models::Indexer;
use crate::test_handler_delegation; use crate::test_handler_delegation;
mod test_handle_scroll_up_and_down {
use rstest::rstest;
use crate::{simple_stateful_iterable_vec, test_iterable_scroll};
use super::*;
test_iterable_scroll!(
test_indexers_scroll,
IndexersHandler,
indexers,
simple_stateful_iterable_vec!(Indexer, String, protocol),
ActiveRadarrBlock::Indexers,
None,
protocol
);
#[rstest]
fn test_indexers_scroll_no_op_when_not_ready(
#[values(
DEFAULT_KEYBINDINGS.up.key, DEFAULT_KEYBINDINGS.down.key
)]
key: Key,
) {
let mut app = App::default();
app.is_loading = true;
app
.data
.radarr_data
.indexers
.set_items(simple_stateful_iterable_vec!(Indexer, String, protocol));
IndexersHandler::with(&key, &mut app, &ActiveRadarrBlock::Indexers, &None).handle();
assert_str_eq!(
app.data.radarr_data.indexers.current_selection().protocol,
"Test 1"
);
IndexersHandler::with(&key, &mut app, &ActiveRadarrBlock::Indexers, &None).handle();
assert_str_eq!(
app.data.radarr_data.indexers.current_selection().protocol,
"Test 1"
);
}
}
mod test_handle_home_end {
use crate::{extended_stateful_iterable_vec, test_iterable_home_and_end};
use super::*;
test_iterable_home_and_end!(
test_indexers_home_end,
IndexersHandler,
indexers,
extended_stateful_iterable_vec!(Indexer, String, protocol),
ActiveRadarrBlock::Indexers,
None,
protocol
);
#[test]
fn test_indexers_home_end_no_op_when_not_ready() {
let mut app = App::default();
app.is_loading = true;
app
.data
.radarr_data
.indexers
.set_items(extended_stateful_iterable_vec!(Indexer, String, protocol));
IndexersHandler::with(
&DEFAULT_KEYBINDINGS.end.key,
&mut app,
&ActiveRadarrBlock::Indexers,
&None,
)
.handle();
assert_str_eq!(
app.data.radarr_data.indexers.current_selection().protocol,
"Test 1"
);
IndexersHandler::with(
&DEFAULT_KEYBINDINGS.home.key,
&mut app,
&ActiveRadarrBlock::Indexers,
&None,
)
.handle();
assert_str_eq!(
app.data.radarr_data.indexers.current_selection().protocol,
"Test 1"
);
}
}
mod test_handle_delete { mod test_handle_delete {
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
@@ -132,11 +32,11 @@ mod tests {
.indexers .indexers
.set_items(vec![Indexer::default()]); .set_items(vec![Indexer::default()]);
IndexersHandler::with(&DELETE_KEY, &mut app, &ActiveRadarrBlock::Indexers, &None).handle(); IndexersHandler::with(DELETE_KEY, &mut app, ActiveRadarrBlock::Indexers, None).handle();
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
&ActiveRadarrBlock::DeleteIndexerPrompt.into() ActiveRadarrBlock::DeleteIndexerPrompt.into()
); );
} }
@@ -151,9 +51,9 @@ mod tests {
.indexers .indexers
.set_items(vec![Indexer::default()]); .set_items(vec![Indexer::default()]);
IndexersHandler::with(&DELETE_KEY, &mut app, &ActiveRadarrBlock::Indexers, &None).handle(); IndexersHandler::with(DELETE_KEY, &mut app, ActiveRadarrBlock::Indexers, None).handle();
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Indexers.into()); assert_eq!(app.get_current_route(), ActiveRadarrBlock::Indexers.into());
} }
} }
@@ -170,20 +70,20 @@ mod tests {
app.data.radarr_data.main_tabs.set_index(5); app.data.radarr_data.main_tabs.set_index(5);
IndexersHandler::with( IndexersHandler::with(
&DEFAULT_KEYBINDINGS.left.key, DEFAULT_KEYBINDINGS.left.key,
&mut app, &mut app,
&ActiveRadarrBlock::Indexers, ActiveRadarrBlock::Indexers,
&None, None,
) )
.handle(); .handle();
assert_eq!( assert_eq!(
app.data.radarr_data.main_tabs.get_active_route(), app.data.radarr_data.main_tabs.get_active_route(),
&ActiveRadarrBlock::RootFolders.into() ActiveRadarrBlock::RootFolders.into()
); );
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
&ActiveRadarrBlock::RootFolders.into() ActiveRadarrBlock::RootFolders.into()
); );
} }
@@ -194,18 +94,18 @@ mod tests {
app.data.radarr_data.main_tabs.set_index(5); app.data.radarr_data.main_tabs.set_index(5);
IndexersHandler::with( IndexersHandler::with(
&DEFAULT_KEYBINDINGS.right.key, DEFAULT_KEYBINDINGS.right.key,
&mut app, &mut app,
&ActiveRadarrBlock::Indexers, ActiveRadarrBlock::Indexers,
&None, None,
) )
.handle(); .handle();
assert_eq!( assert_eq!(
app.data.radarr_data.main_tabs.get_active_route(), app.data.radarr_data.main_tabs.get_active_route(),
&ActiveRadarrBlock::System.into() ActiveRadarrBlock::System.into()
); );
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::System.into()); assert_eq!(app.get_current_route(), ActiveRadarrBlock::System.into());
} }
#[rstest] #[rstest]
@@ -214,40 +114,28 @@ mod tests {
) { ) {
let mut app = App::default(); let mut app = App::default();
IndexersHandler::with( IndexersHandler::with(key, &mut app, ActiveRadarrBlock::DeleteIndexerPrompt, None).handle();
&key,
&mut app,
&ActiveRadarrBlock::DeleteIndexerPrompt,
&None,
)
.handle();
assert!(app.data.radarr_data.prompt_confirm); assert!(app.data.radarr_data.prompt_confirm);
IndexersHandler::with( IndexersHandler::with(key, &mut app, ActiveRadarrBlock::DeleteIndexerPrompt, None).handle();
&key,
&mut app,
&ActiveRadarrBlock::DeleteIndexerPrompt,
&None,
)
.handle();
assert!(!app.data.radarr_data.prompt_confirm); assert!(!app.data.radarr_data.prompt_confirm);
} }
} }
mod test_handle_submit { mod test_handle_submit {
use crate::handlers::radarr_handlers::radarr_handler_test_utils::utils::indexer;
use crate::models::servarr_data::modals::EditIndexerModal; use crate::models::servarr_data::modals::EditIndexerModal;
use crate::models::servarr_data::radarr::radarr_data::{ use crate::models::servarr_data::radarr::radarr_data::{
RadarrData, EDIT_INDEXER_NZB_SELECTION_BLOCKS, EDIT_INDEXER_TORRENT_SELECTION_BLOCKS, RadarrData, EDIT_INDEXER_NZB_SELECTION_BLOCKS, EDIT_INDEXER_TORRENT_SELECTION_BLOCKS,
}; };
use crate::models::servarr_models::{Indexer, IndexerField}; use crate::models::servarr_models::{Indexer, IndexerField};
use crate::network::radarr_network::RadarrEvent;
use bimap::BiMap; use bimap::BiMap;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use serde_json::{Number, Value}; use serde_json::{Number, Value};
use crate::network::radarr_network::RadarrEvent;
use super::*; use super::*;
const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key; const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key;
@@ -306,11 +194,11 @@ mod tests {
radarr_data.indexers.set_items(vec![indexer]); radarr_data.indexers.set_items(vec![indexer]);
app.data.radarr_data = radarr_data; app.data.radarr_data = radarr_data;
IndexersHandler::with(&SUBMIT_KEY, &mut app, &ActiveRadarrBlock::Indexers, &None).handle(); IndexersHandler::with(SUBMIT_KEY, &mut app, ActiveRadarrBlock::Indexers, None).handle();
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
&ActiveRadarrBlock::EditIndexerPrompt.into() ActiveRadarrBlock::EditIndexerPrompt.into()
); );
assert_eq!( assert_eq!(
app.data.radarr_data.edit_indexer_modal, app.data.radarr_data.edit_indexer_modal,
@@ -323,12 +211,12 @@ mod tests {
if torrent_protocol { if torrent_protocol {
assert_eq!( assert_eq!(
app.data.radarr_data.selected_block.blocks, app.data.radarr_data.selected_block.blocks,
&EDIT_INDEXER_TORRENT_SELECTION_BLOCKS EDIT_INDEXER_TORRENT_SELECTION_BLOCKS
); );
} else { } else {
assert_eq!( assert_eq!(
app.data.radarr_data.selected_block.blocks, app.data.radarr_data.selected_block.blocks,
&EDIT_INDEXER_NZB_SELECTION_BLOCKS EDIT_INDEXER_NZB_SELECTION_BLOCKS
); );
} }
} }
@@ -344,38 +232,34 @@ mod tests {
.indexers .indexers
.set_items(vec![Indexer::default()]); .set_items(vec![Indexer::default()]);
IndexersHandler::with(&SUBMIT_KEY, &mut app, &ActiveRadarrBlock::Indexers, &None).handle(); IndexersHandler::with(SUBMIT_KEY, &mut app, ActiveRadarrBlock::Indexers, None).handle();
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Indexers.into()); assert_eq!(app.get_current_route(), ActiveRadarrBlock::Indexers.into());
assert_eq!(app.data.radarr_data.edit_indexer_modal, None); assert_eq!(app.data.radarr_data.edit_indexer_modal, None);
} }
#[test] #[test]
fn test_delete_indexer_prompt_confirm_submit() { fn test_delete_indexer_prompt_confirm_submit() {
let mut app = App::default(); let mut app = App::default();
app app.data.radarr_data.indexers.set_items(vec![indexer()]);
.data
.radarr_data
.indexers
.set_items(vec![Indexer::default()]);
app.data.radarr_data.prompt_confirm = true; app.data.radarr_data.prompt_confirm = true;
app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.push_navigation_stack(ActiveRadarrBlock::Indexers.into());
app.push_navigation_stack(ActiveRadarrBlock::DeleteIndexerPrompt.into()); app.push_navigation_stack(ActiveRadarrBlock::DeleteIndexerPrompt.into());
IndexersHandler::with( IndexersHandler::with(
&SUBMIT_KEY, SUBMIT_KEY,
&mut app, &mut app,
&ActiveRadarrBlock::DeleteIndexerPrompt, ActiveRadarrBlock::DeleteIndexerPrompt,
&None, None,
) )
.handle(); .handle();
assert!(app.data.radarr_data.prompt_confirm); assert!(app.data.radarr_data.prompt_confirm);
assert_eq!( assert_eq!(
app.data.radarr_data.prompt_confirm_action, app.data.radarr_data.prompt_confirm_action,
Some(RadarrEvent::DeleteIndexer(None)) Some(RadarrEvent::DeleteIndexer(1))
); );
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Indexers.into()); assert_eq!(app.get_current_route(), ActiveRadarrBlock::Indexers.into());
} }
#[test] #[test]
@@ -390,16 +274,16 @@ mod tests {
app.push_navigation_stack(ActiveRadarrBlock::DeleteIndexerPrompt.into()); app.push_navigation_stack(ActiveRadarrBlock::DeleteIndexerPrompt.into());
IndexersHandler::with( IndexersHandler::with(
&SUBMIT_KEY, SUBMIT_KEY,
&mut app, &mut app,
&ActiveRadarrBlock::DeleteIndexerPrompt, ActiveRadarrBlock::DeleteIndexerPrompt,
&None, None,
) )
.handle(); .handle();
assert!(!app.data.radarr_data.prompt_confirm); assert!(!app.data.radarr_data.prompt_confirm);
assert_eq!(app.data.radarr_data.prompt_confirm_action, None); assert_eq!(app.data.radarr_data.prompt_confirm_action, None);
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Indexers.into()); assert_eq!(app.get_current_route(), ActiveRadarrBlock::Indexers.into());
} }
} }
@@ -419,14 +303,14 @@ mod tests {
app.data.radarr_data.prompt_confirm = true; app.data.radarr_data.prompt_confirm = true;
IndexersHandler::with( IndexersHandler::with(
&ESC_KEY, ESC_KEY,
&mut app, &mut app,
&ActiveRadarrBlock::DeleteIndexerPrompt, ActiveRadarrBlock::DeleteIndexerPrompt,
&None, None,
) )
.handle(); .handle();
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Indexers.into()); assert_eq!(app.get_current_route(), ActiveRadarrBlock::Indexers.into());
assert!(!app.data.radarr_data.prompt_confirm); assert!(!app.data.radarr_data.prompt_confirm);
} }
@@ -434,14 +318,14 @@ mod tests {
fn test_test_indexer_esc(#[values(true, false)] is_ready: bool) { fn test_test_indexer_esc(#[values(true, false)] is_ready: bool) {
let mut app = App::default(); let mut app = App::default();
app.is_loading = is_ready; app.is_loading = is_ready;
app.data.radarr_data.indexer_test_error = Some("test result".to_owned()); app.data.radarr_data.indexer_test_errors = Some("test result".to_owned());
app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.push_navigation_stack(ActiveRadarrBlock::Indexers.into());
app.push_navigation_stack(ActiveRadarrBlock::TestIndexer.into()); app.push_navigation_stack(ActiveRadarrBlock::TestIndexer.into());
IndexersHandler::with(&ESC_KEY, &mut app, &ActiveRadarrBlock::TestIndexer, &None).handle(); IndexersHandler::with(ESC_KEY, &mut app, ActiveRadarrBlock::TestIndexer, None).handle();
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Indexers.into()); assert_eq!(app.get_current_route(), ActiveRadarrBlock::Indexers.into());
assert_eq!(app.data.radarr_data.indexer_test_error, None); assert_eq!(app.data.radarr_data.indexer_test_errors, None);
} }
#[rstest] #[rstest]
@@ -452,9 +336,9 @@ mod tests {
app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.push_navigation_stack(ActiveRadarrBlock::Indexers.into());
app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.push_navigation_stack(ActiveRadarrBlock::Indexers.into());
IndexersHandler::with(&ESC_KEY, &mut app, &ActiveRadarrBlock::Indexers, &None).handle(); IndexersHandler::with(ESC_KEY, &mut app, ActiveRadarrBlock::Indexers, None).handle();
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Indexers.into()); assert_eq!(app.get_current_route(), ActiveRadarrBlock::Indexers.into());
assert!(app.error.text.is_empty()); assert!(app.error.text.is_empty());
} }
} }
@@ -462,58 +346,13 @@ mod tests {
mod test_handle_key_char { mod test_handle_key_char {
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use super::*;
use crate::handlers::radarr_handlers::radarr_handler_test_utils::utils::indexer;
use crate::{ use crate::{
models::servarr_data::radarr::radarr_data::INDEXER_SETTINGS_SELECTION_BLOCKS, models::servarr_data::radarr::radarr_data::INDEXER_SETTINGS_SELECTION_BLOCKS,
network::radarr_network::RadarrEvent, network::radarr_network::RadarrEvent,
}; };
use super::*;
#[test]
fn test_indexer_add() {
let mut app = App::default();
app
.data
.radarr_data
.indexers
.set_items(vec![Indexer::default()]);
IndexersHandler::with(
&DEFAULT_KEYBINDINGS.add.key,
&mut app,
&ActiveRadarrBlock::Indexers,
&None,
)
.handle();
assert_eq!(
app.get_current_route(),
&ActiveRadarrBlock::AddIndexer.into()
);
}
#[test]
fn test_indexer_add_no_op_when_not_ready() {
let mut app = App::default();
app.is_loading = true;
app.push_navigation_stack(ActiveRadarrBlock::Indexers.into());
app
.data
.radarr_data
.indexers
.set_items(vec![Indexer::default()]);
IndexersHandler::with(
&DEFAULT_KEYBINDINGS.add.key,
&mut app,
&ActiveRadarrBlock::Indexers,
&None,
)
.handle();
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Indexers.into());
}
#[test] #[test]
fn test_refresh_indexers_key() { fn test_refresh_indexers_key() {
let mut app = App::default(); let mut app = App::default();
@@ -525,14 +364,14 @@ mod tests {
app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.push_navigation_stack(ActiveRadarrBlock::Indexers.into());
IndexersHandler::with( IndexersHandler::with(
&DEFAULT_KEYBINDINGS.refresh.key, DEFAULT_KEYBINDINGS.refresh.key,
&mut app, &mut app,
&ActiveRadarrBlock::Indexers, ActiveRadarrBlock::Indexers,
&None, None,
) )
.handle(); .handle();
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Indexers.into()); assert_eq!(app.get_current_route(), ActiveRadarrBlock::Indexers.into());
assert!(app.should_refresh); assert!(app.should_refresh);
} }
@@ -548,14 +387,14 @@ mod tests {
app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.push_navigation_stack(ActiveRadarrBlock::Indexers.into());
IndexersHandler::with( IndexersHandler::with(
&DEFAULT_KEYBINDINGS.refresh.key, DEFAULT_KEYBINDINGS.refresh.key,
&mut app, &mut app,
&ActiveRadarrBlock::Indexers, ActiveRadarrBlock::Indexers,
&None, None,
) )
.handle(); .handle();
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Indexers.into()); assert_eq!(app.get_current_route(), ActiveRadarrBlock::Indexers.into());
assert!(!app.should_refresh); assert!(!app.should_refresh);
} }
@@ -569,20 +408,20 @@ mod tests {
.set_items(vec![Indexer::default()]); .set_items(vec![Indexer::default()]);
IndexersHandler::with( IndexersHandler::with(
&DEFAULT_KEYBINDINGS.settings.key, DEFAULT_KEYBINDINGS.settings.key,
&mut app, &mut app,
&ActiveRadarrBlock::Indexers, ActiveRadarrBlock::Indexers,
&None, None,
) )
.handle(); .handle();
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
&ActiveRadarrBlock::AllIndexerSettingsPrompt.into() ActiveRadarrBlock::AllIndexerSettingsPrompt.into()
); );
assert_eq!( assert_eq!(
app.data.radarr_data.selected_block.blocks, app.data.radarr_data.selected_block.blocks,
&INDEXER_SETTINGS_SELECTION_BLOCKS INDEXER_SETTINGS_SELECTION_BLOCKS
); );
} }
@@ -598,14 +437,14 @@ mod tests {
.set_items(vec![Indexer::default()]); .set_items(vec![Indexer::default()]);
IndexersHandler::with( IndexersHandler::with(
&DEFAULT_KEYBINDINGS.settings.key, DEFAULT_KEYBINDINGS.settings.key,
&mut app, &mut app,
&ActiveRadarrBlock::Indexers, ActiveRadarrBlock::Indexers,
&None, None,
) )
.handle(); .handle();
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Indexers.into()); assert_eq!(app.get_current_route(), ActiveRadarrBlock::Indexers.into());
} }
#[test] #[test]
@@ -618,16 +457,16 @@ mod tests {
.set_items(vec![Indexer::default()]); .set_items(vec![Indexer::default()]);
IndexersHandler::with( IndexersHandler::with(
&DEFAULT_KEYBINDINGS.test.key, DEFAULT_KEYBINDINGS.test.key,
&mut app, &mut app,
&ActiveRadarrBlock::Indexers, ActiveRadarrBlock::Indexers,
&None, None,
) )
.handle(); .handle();
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
&ActiveRadarrBlock::TestIndexer.into() ActiveRadarrBlock::TestIndexer.into()
); );
} }
@@ -643,14 +482,14 @@ mod tests {
.set_items(vec![Indexer::default()]); .set_items(vec![Indexer::default()]);
IndexersHandler::with( IndexersHandler::with(
&DEFAULT_KEYBINDINGS.test.key, DEFAULT_KEYBINDINGS.test.key,
&mut app, &mut app,
&ActiveRadarrBlock::Indexers, ActiveRadarrBlock::Indexers,
&None, None,
) )
.handle(); .handle();
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Indexers.into()); assert_eq!(app.get_current_route(), ActiveRadarrBlock::Indexers.into());
} }
#[test] #[test]
@@ -663,16 +502,16 @@ mod tests {
.set_items(vec![Indexer::default()]); .set_items(vec![Indexer::default()]);
IndexersHandler::with( IndexersHandler::with(
&DEFAULT_KEYBINDINGS.test_all.key, DEFAULT_KEYBINDINGS.test_all.key,
&mut app, &mut app,
&ActiveRadarrBlock::Indexers, ActiveRadarrBlock::Indexers,
&None, None,
) )
.handle(); .handle();
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
&ActiveRadarrBlock::TestAllIndexers.into() ActiveRadarrBlock::TestAllIndexers.into()
); );
} }
@@ -688,41 +527,37 @@ mod tests {
.set_items(vec![Indexer::default()]); .set_items(vec![Indexer::default()]);
IndexersHandler::with( IndexersHandler::with(
&DEFAULT_KEYBINDINGS.test_all.key, DEFAULT_KEYBINDINGS.test_all.key,
&mut app, &mut app,
&ActiveRadarrBlock::Indexers, ActiveRadarrBlock::Indexers,
&None, None,
) )
.handle(); .handle();
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Indexers.into()); assert_eq!(app.get_current_route(), ActiveRadarrBlock::Indexers.into());
} }
#[test] #[test]
fn test_delete_indexer_prompt_confirm() { fn test_delete_indexer_prompt_confirm() {
let mut app = App::default(); let mut app = App::default();
app app.data.radarr_data.indexers.set_items(vec![indexer()]);
.data
.radarr_data
.indexers
.set_items(vec![Indexer::default()]);
app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.push_navigation_stack(ActiveRadarrBlock::Indexers.into());
app.push_navigation_stack(ActiveRadarrBlock::DeleteIndexerPrompt.into()); app.push_navigation_stack(ActiveRadarrBlock::DeleteIndexerPrompt.into());
IndexersHandler::with( IndexersHandler::with(
&DEFAULT_KEYBINDINGS.confirm.key, DEFAULT_KEYBINDINGS.confirm.key,
&mut app, &mut app,
&ActiveRadarrBlock::DeleteIndexerPrompt, ActiveRadarrBlock::DeleteIndexerPrompt,
&None, None,
) )
.handle(); .handle();
assert!(app.data.radarr_data.prompt_confirm); assert!(app.data.radarr_data.prompt_confirm);
assert_eq!( assert_eq!(
app.data.radarr_data.prompt_confirm_action, app.data.radarr_data.prompt_confirm_action,
Some(RadarrEvent::DeleteIndexer(None)) Some(RadarrEvent::DeleteIndexer(1))
); );
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Indexers.into()); assert_eq!(app.get_current_route(), ActiveRadarrBlock::Indexers.into());
} }
} }
@@ -791,23 +626,39 @@ mod tests {
ActiveRadarrBlock::iter().for_each(|active_radarr_block| { ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
if indexers_blocks.contains(&active_radarr_block) { if indexers_blocks.contains(&active_radarr_block) {
assert!(IndexersHandler::accepts(&active_radarr_block)); assert!(IndexersHandler::accepts(active_radarr_block));
} else { } else {
assert!(!IndexersHandler::accepts(&active_radarr_block)); assert!(!IndexersHandler::accepts(active_radarr_block));
} }
}) })
} }
#[test]
fn test_extract_indexer_id() {
let mut app = App::default();
app.data.radarr_data.indexers.set_items(vec![indexer()]);
let indexer_id = IndexersHandler::with(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveRadarrBlock::Indexers,
None,
)
.extract_indexer_id();
assert_eq!(indexer_id, 1);
}
#[test] #[test]
fn test_indexers_handler_not_ready_when_loading() { fn test_indexers_handler_not_ready_when_loading() {
let mut app = App::default(); let mut app = App::default();
app.is_loading = true; app.is_loading = true;
let handler = IndexersHandler::with( let handler = IndexersHandler::with(
&DEFAULT_KEYBINDINGS.esc.key, DEFAULT_KEYBINDINGS.esc.key,
&mut app, &mut app,
&ActiveRadarrBlock::Indexers, ActiveRadarrBlock::Indexers,
&None, None,
); );
assert!(!handler.is_ready()); assert!(!handler.is_ready());
@@ -819,10 +670,10 @@ mod tests {
app.is_loading = false; app.is_loading = false;
let handler = IndexersHandler::with( let handler = IndexersHandler::with(
&DEFAULT_KEYBINDINGS.esc.key, DEFAULT_KEYBINDINGS.esc.key,
&mut app, &mut app,
&ActiveRadarrBlock::Indexers, ActiveRadarrBlock::Indexers,
&None, None,
); );
assert!(!handler.is_ready()); assert!(!handler.is_ready());
@@ -839,10 +690,10 @@ mod tests {
.set_items(vec![Indexer::default()]); .set_items(vec![Indexer::default()]);
let handler = IndexersHandler::with( let handler = IndexersHandler::with(
&DEFAULT_KEYBINDINGS.esc.key, DEFAULT_KEYBINDINGS.esc.key,
&mut app, &mut app,
&ActiveRadarrBlock::Indexers, ActiveRadarrBlock::Indexers,
&None, None,
); );
assert!(handler.is_ready()); assert!(handler.is_ready());
+56 -60
View File
@@ -1,17 +1,19 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handle_table_events;
use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys; use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys;
use crate::handlers::radarr_handlers::indexers::edit_indexer_handler::EditIndexerHandler; use crate::handlers::radarr_handlers::indexers::edit_indexer_handler::EditIndexerHandler;
use crate::handlers::radarr_handlers::indexers::edit_indexer_settings_handler::IndexerSettingsHandler; use crate::handlers::radarr_handlers::indexers::edit_indexer_settings_handler::IndexerSettingsHandler;
use crate::handlers::radarr_handlers::indexers::test_all_indexers_handler::TestAllIndexersHandler; use crate::handlers::radarr_handlers::indexers::test_all_indexers_handler::TestAllIndexersHandler;
use crate::handlers::table_handler::TableHandlingConfig;
use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler}; use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler};
use crate::models::servarr_data::radarr::radarr_data::{ use crate::models::servarr_data::radarr::radarr_data::{
ActiveRadarrBlock, EDIT_INDEXER_NZB_SELECTION_BLOCKS, EDIT_INDEXER_TORRENT_SELECTION_BLOCKS, ActiveRadarrBlock, EDIT_INDEXER_NZB_SELECTION_BLOCKS, EDIT_INDEXER_TORRENT_SELECTION_BLOCKS,
INDEXERS_BLOCKS, INDEXER_SETTINGS_SELECTION_BLOCKS, INDEXERS_BLOCKS, INDEXER_SETTINGS_SELECTION_BLOCKS,
}; };
use crate::models::servarr_models::Indexer;
use crate::models::BlockSelectionState; use crate::models::BlockSelectionState;
use crate::models::Scrollable;
use crate::network::radarr_network::RadarrEvent; use crate::network::radarr_network::RadarrEvent;
mod edit_indexer_handler; mod edit_indexer_handler;
@@ -23,43 +25,56 @@ mod test_all_indexers_handler;
mod indexers_handler_tests; mod indexers_handler_tests;
pub(super) struct IndexersHandler<'a, 'b> { pub(super) struct IndexersHandler<'a, 'b> {
key: &'a Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
active_radarr_block: &'a ActiveRadarrBlock, active_radarr_block: ActiveRadarrBlock,
context: &'a Option<ActiveRadarrBlock>, context: Option<ActiveRadarrBlock>,
}
impl<'a, 'b> IndexersHandler<'a, 'b> {
handle_table_events!(self, indexers, self.app.data.radarr_data.indexers, Indexer);
fn extract_indexer_id(&self) -> i64 {
self.app.data.radarr_data.indexers.current_selection().id
}
} }
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexersHandler<'a, 'b> { impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexersHandler<'a, 'b> {
fn handle(&mut self) { fn handle(&mut self) {
match self.active_radarr_block { let indexer_table_handling_config =
_ if EditIndexerHandler::accepts(self.active_radarr_block) => { TableHandlingConfig::new(ActiveRadarrBlock::Indexers.into());
EditIndexerHandler::with(self.key, self.app, self.active_radarr_block, self.context)
.handle() if !self.handle_indexers_table_events(indexer_table_handling_config) {
match self.active_radarr_block {
_ if EditIndexerHandler::accepts(self.active_radarr_block) => {
EditIndexerHandler::with(self.key, self.app, self.active_radarr_block, self.context)
.handle()
}
_ if IndexerSettingsHandler::accepts(self.active_radarr_block) => {
IndexerSettingsHandler::with(self.key, self.app, self.active_radarr_block, self.context)
.handle()
}
_ if TestAllIndexersHandler::accepts(self.active_radarr_block) => {
TestAllIndexersHandler::with(self.key, self.app, self.active_radarr_block, self.context)
.handle()
}
_ => self.handle_key_event(),
} }
_ if IndexerSettingsHandler::accepts(self.active_radarr_block) => {
IndexerSettingsHandler::with(self.key, self.app, self.active_radarr_block, self.context)
.handle()
}
_ if TestAllIndexersHandler::accepts(self.active_radarr_block) => {
TestAllIndexersHandler::with(self.key, self.app, self.active_radarr_block, self.context)
.handle()
}
_ => self.handle_key_event(),
} }
} }
fn accepts(active_block: &'a ActiveRadarrBlock) -> bool { fn accepts(active_block: ActiveRadarrBlock) -> bool {
EditIndexerHandler::accepts(active_block) EditIndexerHandler::accepts(active_block)
|| IndexerSettingsHandler::accepts(active_block) || IndexerSettingsHandler::accepts(active_block)
|| TestAllIndexersHandler::accepts(active_block) || TestAllIndexersHandler::accepts(active_block)
|| INDEXERS_BLOCKS.contains(active_block) || INDEXERS_BLOCKS.contains(&active_block)
} }
fn with( fn with(
key: &'a Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
active_block: &'a ActiveRadarrBlock, active_block: ActiveRadarrBlock,
context: &'a Option<ActiveRadarrBlock>, context: Option<ActiveRadarrBlock>,
) -> IndexersHandler<'a, 'b> { ) -> IndexersHandler<'a, 'b> {
IndexersHandler { IndexersHandler {
key, key,
@@ -69,7 +84,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexersHandler<'a,
} }
} }
fn get_key(&self) -> &Key { fn get_key(&self) -> Key {
self.key self.key
} }
@@ -77,32 +92,16 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexersHandler<'a,
!self.app.is_loading && !self.app.data.radarr_data.indexers.is_empty() !self.app.is_loading && !self.app.data.radarr_data.indexers.is_empty()
} }
fn handle_scroll_up(&mut self) { fn handle_scroll_up(&mut self) {}
if self.active_radarr_block == &ActiveRadarrBlock::Indexers {
self.app.data.radarr_data.indexers.scroll_up();
}
}
fn handle_scroll_down(&mut self) { fn handle_scroll_down(&mut self) {}
if self.active_radarr_block == &ActiveRadarrBlock::Indexers {
self.app.data.radarr_data.indexers.scroll_down();
}
}
fn handle_home(&mut self) { fn handle_home(&mut self) {}
if self.active_radarr_block == &ActiveRadarrBlock::Indexers {
self.app.data.radarr_data.indexers.scroll_to_top();
}
}
fn handle_end(&mut self) { fn handle_end(&mut self) {}
if self.active_radarr_block == &ActiveRadarrBlock::Indexers {
self.app.data.radarr_data.indexers.scroll_to_bottom();
}
}
fn handle_delete(&mut self) { fn handle_delete(&mut self) {
if self.active_radarr_block == &ActiveRadarrBlock::Indexers { if self.active_radarr_block == ActiveRadarrBlock::Indexers {
self self
.app .app
.push_navigation_stack(ActiveRadarrBlock::DeleteIndexerPrompt.into()); .push_navigation_stack(ActiveRadarrBlock::DeleteIndexerPrompt.into());
@@ -120,9 +119,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexersHandler<'a,
fn handle_submit(&mut self) { fn handle_submit(&mut self) {
match self.active_radarr_block { match self.active_radarr_block {
ActiveRadarrBlock::DeleteIndexerPrompt => { ActiveRadarrBlock::DeleteIndexerPrompt => {
let indexer_id = self.extract_indexer_id();
let radarr_data = &mut self.app.data.radarr_data; let radarr_data = &mut self.app.data.radarr_data;
if radarr_data.prompt_confirm { if radarr_data.prompt_confirm {
radarr_data.prompt_confirm_action = Some(RadarrEvent::DeleteIndexer(None)); radarr_data.prompt_confirm_action = Some(RadarrEvent::DeleteIndexer(indexer_id));
} }
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
@@ -141,10 +141,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexersHandler<'a,
.protocol; .protocol;
if protocol == "torrent" { if protocol == "torrent" {
self.app.data.radarr_data.selected_block = self.app.data.radarr_data.selected_block =
BlockSelectionState::new(&EDIT_INDEXER_TORRENT_SELECTION_BLOCKS); BlockSelectionState::new(EDIT_INDEXER_TORRENT_SELECTION_BLOCKS);
} else { } else {
self.app.data.radarr_data.selected_block = self.app.data.radarr_data.selected_block =
BlockSelectionState::new(&EDIT_INDEXER_NZB_SELECTION_BLOCKS); BlockSelectionState::new(EDIT_INDEXER_NZB_SELECTION_BLOCKS);
} }
} }
_ => (), _ => (),
@@ -159,7 +159,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexersHandler<'a,
} }
ActiveRadarrBlock::TestIndexer => { ActiveRadarrBlock::TestIndexer => {
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
self.app.data.radarr_data.indexer_test_error = None; self.app.data.radarr_data.indexer_test_errors = None;
} }
_ => handle_clear_errors(self.app), _ => handle_clear_errors(self.app),
} }
@@ -169,37 +169,33 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexersHandler<'a,
let key = self.key; let key = self.key;
match self.active_radarr_block { match self.active_radarr_block {
ActiveRadarrBlock::Indexers => match self.key { ActiveRadarrBlock::Indexers => match self.key {
_ if *key == DEFAULT_KEYBINDINGS.add.key => { _ if key == DEFAULT_KEYBINDINGS.refresh.key => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::AddIndexer.into());
}
_ if *key == DEFAULT_KEYBINDINGS.refresh.key => {
self.app.should_refresh = true; self.app.should_refresh = true;
} }
_ if *key == DEFAULT_KEYBINDINGS.test.key => { _ if key == DEFAULT_KEYBINDINGS.test.key => {
self self
.app .app
.push_navigation_stack(ActiveRadarrBlock::TestIndexer.into()); .push_navigation_stack(ActiveRadarrBlock::TestIndexer.into());
} }
_ if *key == DEFAULT_KEYBINDINGS.test_all.key => { _ if key == DEFAULT_KEYBINDINGS.test_all.key => {
self self
.app .app
.push_navigation_stack(ActiveRadarrBlock::TestAllIndexers.into()); .push_navigation_stack(ActiveRadarrBlock::TestAllIndexers.into());
} }
_ if *key == DEFAULT_KEYBINDINGS.settings.key => { _ if key == DEFAULT_KEYBINDINGS.settings.key => {
self self
.app .app
.push_navigation_stack(ActiveRadarrBlock::AllIndexerSettingsPrompt.into()); .push_navigation_stack(ActiveRadarrBlock::AllIndexerSettingsPrompt.into());
self.app.data.radarr_data.selected_block = self.app.data.radarr_data.selected_block =
BlockSelectionState::new(&INDEXER_SETTINGS_SELECTION_BLOCKS); BlockSelectionState::new(INDEXER_SETTINGS_SELECTION_BLOCKS);
} }
_ => (), _ => (),
}, },
ActiveRadarrBlock::DeleteIndexerPrompt => { ActiveRadarrBlock::DeleteIndexerPrompt => {
if *key == DEFAULT_KEYBINDINGS.confirm.key { if key == DEFAULT_KEYBINDINGS.confirm.key {
self.app.data.radarr_data.prompt_confirm = true; self.app.data.radarr_data.prompt_confirm = true;
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DeleteIndexer(None)); self.app.data.radarr_data.prompt_confirm_action =
Some(RadarrEvent::DeleteIndexer(self.extract_indexer_id()));
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
} }
@@ -1,30 +1,58 @@
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handle_table_events;
use crate::handlers::table_handler::TableHandlingConfig;
use crate::handlers::KeyEventHandler; use crate::handlers::KeyEventHandler;
use crate::models::servarr_data::modals::IndexerTestResultModalItem;
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock; use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
use crate::models::Scrollable;
#[cfg(test)] #[cfg(test)]
#[path = "test_all_indexers_handler_tests.rs"] #[path = "test_all_indexers_handler_tests.rs"]
mod test_all_indexers_handler_tests; mod test_all_indexers_handler_tests;
pub(super) struct TestAllIndexersHandler<'a, 'b> { pub(super) struct TestAllIndexersHandler<'a, 'b> {
key: &'a Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
active_radarr_block: &'a ActiveRadarrBlock, active_radarr_block: ActiveRadarrBlock,
_context: &'a Option<ActiveRadarrBlock>, _context: Option<ActiveRadarrBlock>,
}
impl<'a, 'b> TestAllIndexersHandler<'a, 'b> {
handle_table_events!(
self,
indexer_test_all_results,
self
.app
.data
.radarr_data
.indexer_test_all_results
.as_mut()
.unwrap(),
IndexerTestResultModalItem
);
} }
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for TestAllIndexersHandler<'a, 'b> { impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for TestAllIndexersHandler<'a, 'b> {
fn accepts(active_block: &'a ActiveRadarrBlock) -> bool { fn handle(&mut self) {
active_block == &ActiveRadarrBlock::TestAllIndexers let test_all_indexers_test_results_table_handler_config =
TableHandlingConfig::new(ActiveRadarrBlock::TestAllIndexers.into());
if !self.handle_indexer_test_all_results_table_events(
test_all_indexers_test_results_table_handler_config,
) {
self.handle_key_event();
}
}
fn accepts(active_block: ActiveRadarrBlock) -> bool {
active_block == ActiveRadarrBlock::TestAllIndexers
} }
fn with( fn with(
key: &'a Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
active_block: &'a ActiveRadarrBlock, active_block: ActiveRadarrBlock,
_context: &'a Option<ActiveRadarrBlock>, _context: Option<ActiveRadarrBlock>,
) -> TestAllIndexersHandler<'a, 'b> { ) -> TestAllIndexersHandler<'a, 'b> {
TestAllIndexersHandler { TestAllIndexersHandler {
key, key,
@@ -34,7 +62,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for TestAllIndexersHandl
} }
} }
fn get_key(&self) -> &Key { fn get_key(&self) -> Key {
self.key self.key
} }
@@ -48,57 +76,13 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for TestAllIndexersHandl
!self.app.is_loading && table_is_ready !self.app.is_loading && table_is_ready
} }
fn handle_scroll_up(&mut self) { fn handle_scroll_up(&mut self) {}
if self.active_radarr_block == &ActiveRadarrBlock::TestAllIndexers {
self
.app
.data
.radarr_data
.indexer_test_all_results
.as_mut()
.unwrap()
.scroll_up()
}
}
fn handle_scroll_down(&mut self) { fn handle_scroll_down(&mut self) {}
if self.active_radarr_block == &ActiveRadarrBlock::TestAllIndexers {
self
.app
.data
.radarr_data
.indexer_test_all_results
.as_mut()
.unwrap()
.scroll_down()
}
}
fn handle_home(&mut self) { fn handle_home(&mut self) {}
if self.active_radarr_block == &ActiveRadarrBlock::TestAllIndexers {
self
.app
.data
.radarr_data
.indexer_test_all_results
.as_mut()
.unwrap()
.scroll_to_top()
}
}
fn handle_end(&mut self) { fn handle_end(&mut self) {}
if self.active_radarr_block == &ActiveRadarrBlock::TestAllIndexers {
self
.app
.data
.radarr_data
.indexer_test_all_results
.as_mut()
.unwrap()
.scroll_to_bottom()
}
}
fn handle_delete(&mut self) {} fn handle_delete(&mut self) {}
@@ -107,7 +91,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for TestAllIndexersHandl
fn handle_submit(&mut self) {} fn handle_submit(&mut self) {}
fn handle_esc(&mut self) { fn handle_esc(&mut self) {
if self.active_radarr_block == &ActiveRadarrBlock::TestAllIndexers { if self.active_radarr_block == ActiveRadarrBlock::TestAllIndexers {
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
self.app.data.radarr_data.indexer_test_all_results = None; self.app.data.radarr_data.indexer_test_all_results = None;
} }
@@ -2,7 +2,6 @@
mod tests { mod tests {
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key;
use crate::handlers::radarr_handlers::indexers::test_all_indexers_handler::TestAllIndexersHandler; use crate::handlers::radarr_handlers::indexers::test_all_indexers_handler::TestAllIndexersHandler;
use crate::handlers::KeyEventHandler; use crate::handlers::KeyEventHandler;
use crate::models::servarr_data::modals::IndexerTestResultModalItem; use crate::models::servarr_data::modals::IndexerTestResultModalItem;
@@ -10,220 +9,6 @@ mod tests {
use crate::models::stateful_table::StatefulTable; use crate::models::stateful_table::StatefulTable;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
mod test_handle_scroll_up_and_down {
use pretty_assertions::assert_str_eq;
use rstest::rstest;
use crate::models::servarr_data::modals::IndexerTestResultModalItem;
use crate::models::stateful_table::StatefulTable;
use crate::simple_stateful_iterable_vec;
use super::*;
#[rstest]
fn test_test_all_indexers_results_scroll(
#[values(DEFAULT_KEYBINDINGS.up.key, DEFAULT_KEYBINDINGS.down.key)] key: Key,
) {
let mut app = App::default();
let mut indexer_test_results = StatefulTable::default();
indexer_test_results.set_items(simple_stateful_iterable_vec!(
IndexerTestResultModalItem,
String,
name
));
app.data.radarr_data.indexer_test_all_results = Some(indexer_test_results);
TestAllIndexersHandler::with(&key, &mut app, &ActiveRadarrBlock::TestAllIndexers, &None)
.handle();
assert_str_eq!(
app
.data
.radarr_data
.indexer_test_all_results
.as_ref()
.unwrap()
.current_selection()
.name,
"Test 2"
);
TestAllIndexersHandler::with(&key, &mut app, &ActiveRadarrBlock::TestAllIndexers, &None)
.handle();
assert_str_eq!(
app
.data
.radarr_data
.indexer_test_all_results
.as_ref()
.unwrap()
.current_selection()
.name,
"Test 1"
);
}
#[rstest]
fn test_test_all_indexers_results_scroll_no_op_when_not_ready(
#[values(DEFAULT_KEYBINDINGS.up.key, DEFAULT_KEYBINDINGS.down.key)] key: Key,
) {
let mut app = App::default();
app.is_loading = true;
let mut indexer_test_results = StatefulTable::default();
indexer_test_results.set_items(simple_stateful_iterable_vec!(
IndexerTestResultModalItem,
String,
name
));
app.data.radarr_data.indexer_test_all_results = Some(indexer_test_results);
TestAllIndexersHandler::with(&key, &mut app, &ActiveRadarrBlock::TestAllIndexers, &None)
.handle();
assert_str_eq!(
app
.data
.radarr_data
.indexer_test_all_results
.as_ref()
.unwrap()
.current_selection()
.name,
"Test 1"
);
TestAllIndexersHandler::with(&key, &mut app, &ActiveRadarrBlock::TestAllIndexers, &None)
.handle();
assert_str_eq!(
app
.data
.radarr_data
.indexer_test_all_results
.as_ref()
.unwrap()
.current_selection()
.name,
"Test 1"
);
}
}
mod test_handle_home_end {
use crate::extended_stateful_iterable_vec;
use crate::models::servarr_data::modals::IndexerTestResultModalItem;
use crate::models::stateful_table::StatefulTable;
use pretty_assertions::assert_str_eq;
use super::*;
#[test]
fn test_test_all_indexers_results_home_end() {
let mut app = App::default();
let mut indexer_test_results = StatefulTable::default();
indexer_test_results.set_items(extended_stateful_iterable_vec!(
IndexerTestResultModalItem,
String,
name
));
app.data.radarr_data.indexer_test_all_results = Some(indexer_test_results);
TestAllIndexersHandler::with(
&DEFAULT_KEYBINDINGS.end.key,
&mut app,
&ActiveRadarrBlock::TestAllIndexers,
&None,
)
.handle();
assert_str_eq!(
app
.data
.radarr_data
.indexer_test_all_results
.as_ref()
.unwrap()
.current_selection()
.name,
"Test 3"
);
TestAllIndexersHandler::with(
&DEFAULT_KEYBINDINGS.home.key,
&mut app,
&ActiveRadarrBlock::TestAllIndexers,
&None,
)
.handle();
assert_str_eq!(
app
.data
.radarr_data
.indexer_test_all_results
.as_ref()
.unwrap()
.current_selection()
.name,
"Test 1"
);
}
#[test]
fn test_test_all_indexers_results_home_end_no_op_when_not_ready() {
let mut app = App::default();
app.is_loading = true;
let mut indexer_test_results = StatefulTable::default();
indexer_test_results.set_items(extended_stateful_iterable_vec!(
IndexerTestResultModalItem,
String,
name
));
app.data.radarr_data.indexer_test_all_results = Some(indexer_test_results);
TestAllIndexersHandler::with(
&DEFAULT_KEYBINDINGS.end.key,
&mut app,
&ActiveRadarrBlock::TestAllIndexers,
&None,
)
.handle();
assert_str_eq!(
app
.data
.radarr_data
.indexer_test_all_results
.as_ref()
.unwrap()
.current_selection()
.name,
"Test 1"
);
TestAllIndexersHandler::with(
&DEFAULT_KEYBINDINGS.home.key,
&mut app,
&ActiveRadarrBlock::TestAllIndexers,
&None,
)
.handle();
assert_str_eq!(
app
.data
.radarr_data
.indexer_test_all_results
.as_ref()
.unwrap()
.current_selection()
.name,
"Test 1"
);
}
}
mod test_handle_esc { mod test_handle_esc {
use super::*; use super::*;
use crate::models::stateful_table::StatefulTable; use crate::models::stateful_table::StatefulTable;
@@ -239,14 +24,14 @@ mod tests {
app.data.radarr_data.indexer_test_all_results = Some(StatefulTable::default()); app.data.radarr_data.indexer_test_all_results = Some(StatefulTable::default());
TestAllIndexersHandler::with( TestAllIndexersHandler::with(
&DEFAULT_KEYBINDINGS.esc.key, DEFAULT_KEYBINDINGS.esc.key,
&mut app, &mut app,
&ActiveRadarrBlock::TestAllIndexers, ActiveRadarrBlock::TestAllIndexers,
&None, None,
) )
.handle(); .handle();
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Indexers.into()); assert_eq!(app.get_current_route(), ActiveRadarrBlock::Indexers.into());
assert!(!app.data.radarr_data.prompt_confirm); assert!(!app.data.radarr_data.prompt_confirm);
assert!(app.data.radarr_data.indexer_test_all_results.is_none()); assert!(app.data.radarr_data.indexer_test_all_results.is_none());
} }
@@ -256,9 +41,9 @@ mod tests {
fn test_test_all_indexers_handler_accepts() { fn test_test_all_indexers_handler_accepts() {
ActiveRadarrBlock::iter().for_each(|active_radarr_block| { ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
if active_radarr_block == ActiveRadarrBlock::TestAllIndexers { if active_radarr_block == ActiveRadarrBlock::TestAllIndexers {
assert!(TestAllIndexersHandler::accepts(&active_radarr_block)); assert!(TestAllIndexersHandler::accepts(active_radarr_block));
} else { } else {
assert!(!TestAllIndexersHandler::accepts(&active_radarr_block)); assert!(!TestAllIndexersHandler::accepts(active_radarr_block));
} }
}); });
} }
@@ -269,10 +54,10 @@ mod tests {
app.is_loading = true; app.is_loading = true;
let handler = TestAllIndexersHandler::with( let handler = TestAllIndexersHandler::with(
&DEFAULT_KEYBINDINGS.esc.key, DEFAULT_KEYBINDINGS.esc.key,
&mut app, &mut app,
&ActiveRadarrBlock::TestAllIndexers, ActiveRadarrBlock::TestAllIndexers,
&None, None,
); );
assert!(!handler.is_ready()); assert!(!handler.is_ready());
@@ -284,10 +69,10 @@ mod tests {
app.is_loading = false; app.is_loading = false;
let handler = TestAllIndexersHandler::with( let handler = TestAllIndexersHandler::with(
&DEFAULT_KEYBINDINGS.esc.key, DEFAULT_KEYBINDINGS.esc.key,
&mut app, &mut app,
&ActiveRadarrBlock::TestAllIndexers, ActiveRadarrBlock::TestAllIndexers,
&None, None,
); );
assert!(!handler.is_ready()); assert!(!handler.is_ready());
@@ -300,10 +85,10 @@ mod tests {
app.data.radarr_data.indexer_test_all_results = Some(StatefulTable::default()); app.data.radarr_data.indexer_test_all_results = Some(StatefulTable::default());
let handler = TestAllIndexersHandler::with( let handler = TestAllIndexersHandler::with(
&DEFAULT_KEYBINDINGS.esc.key, DEFAULT_KEYBINDINGS.esc.key,
&mut app, &mut app,
&ActiveRadarrBlock::TestAllIndexers, ActiveRadarrBlock::TestAllIndexers,
&None, None,
); );
assert!(!handler.is_ready()); assert!(!handler.is_ready());
@@ -318,10 +103,10 @@ mod tests {
app.data.radarr_data.indexer_test_all_results = Some(indexer_test_results); app.data.radarr_data.indexer_test_all_results = Some(indexer_test_results);
let handler = TestAllIndexersHandler::with( let handler = TestAllIndexersHandler::with(
&DEFAULT_KEYBINDINGS.esc.key, DEFAULT_KEYBINDINGS.esc.key,
&mut app, &mut app,
&ActiveRadarrBlock::TestAllIndexers, ActiveRadarrBlock::TestAllIndexers,
&None, None,
); );
assert!(handler.is_ready()); assert!(handler.is_ready());
@@ -1,33 +1,149 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::handlers::table_handler::TableHandlingConfig;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
use crate::models::radarr_models::{
AddMovieBody, AddMovieOptions, AddMovieSearchResult, CollectionMovie,
};
use crate::models::servarr_data::radarr::modals::AddMovieModal;
use crate::models::servarr_data::radarr::radarr_data::{ use crate::models::servarr_data::radarr::radarr_data::{
ActiveRadarrBlock, ADD_MOVIE_BLOCKS, ADD_MOVIE_SELECTION_BLOCKS, ActiveRadarrBlock, ADD_MOVIE_BLOCKS, ADD_MOVIE_SELECTION_BLOCKS,
}; };
use crate::models::stateful_table::StatefulTable;
use crate::models::{BlockSelectionState, Scrollable}; use crate::models::{BlockSelectionState, Scrollable};
use crate::network::radarr_network::RadarrEvent; use crate::network::radarr_network::RadarrEvent;
use crate::{handle_text_box_keys, handle_text_box_left_right_keys, App, Key}; use crate::{handle_table_events, handle_text_box_keys, handle_text_box_left_right_keys, App, Key};
#[cfg(test)] #[cfg(test)]
#[path = "add_movie_handler_tests.rs"] #[path = "add_movie_handler_tests.rs"]
mod add_movie_handler_tests; mod add_movie_handler_tests;
pub(super) struct AddMovieHandler<'a, 'b> { pub(super) struct AddMovieHandler<'a, 'b> {
key: &'a Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
active_radarr_block: &'a ActiveRadarrBlock, active_radarr_block: ActiveRadarrBlock,
context: &'a Option<ActiveRadarrBlock>, context: Option<ActiveRadarrBlock>,
}
impl<'a, 'b> AddMovieHandler<'a, 'b> {
handle_table_events!(
self,
add_movie_search_results,
self
.app
.data
.radarr_data
.add_searched_movies
.as_mut()
.unwrap_or(&mut StatefulTable::default()),
AddMovieSearchResult
);
fn build_add_movie_body(&mut self) -> AddMovieBody {
let tags = self
.app
.data
.radarr_data
.add_movie_modal
.as_ref()
.unwrap()
.tags
.text
.clone();
let AddMovieModal {
root_folder_list,
monitor_list,
minimum_availability_list,
quality_profile_list,
..
} = self.app.data.radarr_data.add_movie_modal.as_ref().unwrap();
let (tmdb_id, title) = if let Some(context) = self.context {
if context == ActiveRadarrBlock::CollectionDetails {
let CollectionMovie { tmdb_id, title, .. } = self
.app
.data
.radarr_data
.collection_movies
.current_selection()
.clone();
(tmdb_id, title.text)
} else {
let AddMovieSearchResult { tmdb_id, title, .. } = self
.app
.data
.radarr_data
.add_searched_movies
.as_ref()
.unwrap()
.current_selection()
.clone();
(tmdb_id, title.text)
}
} else {
let AddMovieSearchResult { tmdb_id, title, .. } = self
.app
.data
.radarr_data
.add_searched_movies
.as_ref()
.unwrap()
.current_selection()
.clone();
(tmdb_id, title.text)
};
let quality_profile = quality_profile_list.current_selection();
let quality_profile_id = *self
.app
.data
.radarr_data
.quality_profile_map
.iter()
.filter(|(_, value)| *value == quality_profile)
.map(|(key, _)| key)
.next()
.unwrap();
let path = root_folder_list.current_selection().path.clone();
let monitor = monitor_list.current_selection().to_string();
let minimum_availability = minimum_availability_list.current_selection().to_string();
self.app.data.radarr_data.add_movie_modal = None;
AddMovieBody {
tmdb_id,
title,
root_folder_path: path,
minimum_availability,
monitored: true,
quality_profile_id,
tags: Vec::new(),
tag_input_string: Some(tags),
add_options: AddMovieOptions {
monitor,
search_for_movie: true,
},
}
}
} }
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a, 'b> { impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a, 'b> {
fn accepts(active_block: &'a ActiveRadarrBlock) -> bool { fn handle(&mut self) {
ADD_MOVIE_BLOCKS.contains(active_block) let add_movie_table_handling_config =
TableHandlingConfig::new(ActiveRadarrBlock::AddMovieSearchResults.into());
if !self.handle_add_movie_search_results_table_events(add_movie_table_handling_config) {
self.handle_key_event();
}
}
fn accepts(active_block: ActiveRadarrBlock) -> bool {
ADD_MOVIE_BLOCKS.contains(&active_block)
} }
fn with( fn with(
key: &'a Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
active_block: &'a ActiveRadarrBlock, active_block: ActiveRadarrBlock,
context: &'a Option<ActiveRadarrBlock>, context: Option<ActiveRadarrBlock>,
) -> AddMovieHandler<'a, 'b> { ) -> AddMovieHandler<'a, 'b> {
AddMovieHandler { AddMovieHandler {
key, key,
@@ -37,7 +153,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a,
} }
} }
fn get_key(&self) -> &Key { fn get_key(&self) -> Key {
self.key self.key
} }
@@ -47,14 +163,6 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a,
fn handle_scroll_up(&mut self) { fn handle_scroll_up(&mut self) {
match self.active_radarr_block { match self.active_radarr_block {
ActiveRadarrBlock::AddMovieSearchResults => self
.app
.data
.radarr_data
.add_searched_movies
.as_mut()
.unwrap()
.scroll_up(),
ActiveRadarrBlock::AddMovieSelectMonitor => self ActiveRadarrBlock::AddMovieSelectMonitor => self
.app .app
.data .data
@@ -91,21 +199,13 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a,
.unwrap() .unwrap()
.root_folder_list .root_folder_list
.scroll_up(), .scroll_up(),
ActiveRadarrBlock::AddMoviePrompt => self.app.data.radarr_data.selected_block.previous(), ActiveRadarrBlock::AddMoviePrompt => self.app.data.radarr_data.selected_block.up(),
_ => (), _ => (),
} }
} }
fn handle_scroll_down(&mut self) { fn handle_scroll_down(&mut self) {
match self.active_radarr_block { match self.active_radarr_block {
ActiveRadarrBlock::AddMovieSearchResults => self
.app
.data
.radarr_data
.add_searched_movies
.as_mut()
.unwrap()
.scroll_down(),
ActiveRadarrBlock::AddMovieSelectMonitor => self ActiveRadarrBlock::AddMovieSelectMonitor => self
.app .app
.data .data
@@ -142,21 +242,13 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a,
.unwrap() .unwrap()
.root_folder_list .root_folder_list
.scroll_down(), .scroll_down(),
ActiveRadarrBlock::AddMoviePrompt => self.app.data.radarr_data.selected_block.next(), ActiveRadarrBlock::AddMoviePrompt => self.app.data.radarr_data.selected_block.down(),
_ => (), _ => (),
} }
} }
fn handle_home(&mut self) { fn handle_home(&mut self) {
match self.active_radarr_block { match self.active_radarr_block {
ActiveRadarrBlock::AddMovieSearchResults => self
.app
.data
.radarr_data
.add_searched_movies
.as_mut()
.unwrap()
.scroll_to_top(),
ActiveRadarrBlock::AddMovieSelectMonitor => self ActiveRadarrBlock::AddMovieSelectMonitor => self
.app .app
.data .data
@@ -216,14 +308,6 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a,
fn handle_end(&mut self) { fn handle_end(&mut self) {
match self.active_radarr_block { match self.active_radarr_block {
ActiveRadarrBlock::AddMovieSearchResults => self
.app
.data
.radarr_data
.add_searched_movies
.as_mut()
.unwrap()
.scroll_to_bottom(),
ActiveRadarrBlock::AddMovieSelectMonitor => self ActiveRadarrBlock::AddMovieSelectMonitor => self
.app .app
.data .data
@@ -313,7 +397,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a,
fn handle_submit(&mut self) { fn handle_submit(&mut self) {
match self.active_radarr_block { match self.active_radarr_block {
_ if *self.active_radarr_block == ActiveRadarrBlock::AddMovieSearchInput _ if self.active_radarr_block == ActiveRadarrBlock::AddMovieSearchInput
&& !self && !self
.app .app
.data .data
@@ -329,7 +413,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a,
.push_navigation_stack(ActiveRadarrBlock::AddMovieSearchResults.into()); .push_navigation_stack(ActiveRadarrBlock::AddMovieSearchResults.into());
self.app.should_ignore_quit_key = false; self.app.should_ignore_quit_key = false;
} }
_ if *self.active_radarr_block == ActiveRadarrBlock::AddMovieSearchResults _ if self.active_radarr_block == ActiveRadarrBlock::AddMovieSearchResults
&& self.app.data.radarr_data.add_searched_movies.is_some() => && self.app.data.radarr_data.add_searched_movies.is_some() =>
{ {
let tmdb_id = self let tmdb_id = self
@@ -360,14 +444,15 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a,
.push_navigation_stack(ActiveRadarrBlock::AddMoviePrompt.into()); .push_navigation_stack(ActiveRadarrBlock::AddMoviePrompt.into());
self.app.data.radarr_data.add_movie_modal = Some((&self.app.data.radarr_data).into()); self.app.data.radarr_data.add_movie_modal = Some((&self.app.data.radarr_data).into());
self.app.data.radarr_data.selected_block = self.app.data.radarr_data.selected_block =
BlockSelectionState::new(&ADD_MOVIE_SELECTION_BLOCKS); BlockSelectionState::new(ADD_MOVIE_SELECTION_BLOCKS);
} }
} }
ActiveRadarrBlock::AddMoviePrompt => { ActiveRadarrBlock::AddMoviePrompt => {
match self.app.data.radarr_data.selected_block.get_active_block() { match self.app.data.radarr_data.selected_block.get_active_block() {
ActiveRadarrBlock::AddMovieConfirmPrompt => { ActiveRadarrBlock::AddMovieConfirmPrompt => {
if self.app.data.radarr_data.prompt_confirm { if self.app.data.radarr_data.prompt_confirm {
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::AddMovie(None)); self.app.data.radarr_data.prompt_confirm_action =
Some(RadarrEvent::AddMovie(self.build_add_movie_body()));
} }
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
@@ -377,16 +462,16 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a,
| ActiveRadarrBlock::AddMovieSelectQualityProfile | ActiveRadarrBlock::AddMovieSelectQualityProfile
| ActiveRadarrBlock::AddMovieSelectRootFolder => self.app.push_navigation_stack( | ActiveRadarrBlock::AddMovieSelectRootFolder => self.app.push_navigation_stack(
( (
*self.app.data.radarr_data.selected_block.get_active_block(), self.app.data.radarr_data.selected_block.get_active_block(),
*self.context, self.context,
) )
.into(), .into(),
), ),
ActiveRadarrBlock::AddMovieTagsInput => { ActiveRadarrBlock::AddMovieTagsInput => {
self.app.push_navigation_stack( self.app.push_navigation_stack(
( (
*self.app.data.radarr_data.selected_block.get_active_block(), self.app.data.radarr_data.selected_block.get_active_block(),
*self.context, self.context,
) )
.into(), .into(),
); );
@@ -463,11 +548,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a,
} }
ActiveRadarrBlock::AddMoviePrompt => { ActiveRadarrBlock::AddMoviePrompt => {
if self.app.data.radarr_data.selected_block.get_active_block() if self.app.data.radarr_data.selected_block.get_active_block()
== &ActiveRadarrBlock::AddMovieConfirmPrompt == ActiveRadarrBlock::AddMovieConfirmPrompt
&& *key == DEFAULT_KEYBINDINGS.confirm.key && key == DEFAULT_KEYBINDINGS.confirm.key
{ {
self.app.data.radarr_data.prompt_confirm = true; self.app.data.radarr_data.prompt_confirm = true;
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::AddMovie(None)); self.app.data.radarr_data.prompt_confirm_action =
Some(RadarrEvent::AddMovie(self.build_add_movie_body()));
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
} }
} }
File diff suppressed because it is too large Load Diff
@@ -2,6 +2,7 @@ use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
use crate::models::radarr_models::DeleteMovieParams;
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, DELETE_MOVIE_BLOCKS}; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, DELETE_MOVIE_BLOCKS};
use crate::network::radarr_network::RadarrEvent; use crate::network::radarr_network::RadarrEvent;
@@ -10,22 +11,37 @@ use crate::network::radarr_network::RadarrEvent;
mod delete_movie_handler_tests; mod delete_movie_handler_tests;
pub(super) struct DeleteMovieHandler<'a, 'b> { pub(super) struct DeleteMovieHandler<'a, 'b> {
key: &'a Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
active_radarr_block: &'a ActiveRadarrBlock, active_radarr_block: ActiveRadarrBlock,
_context: &'a Option<ActiveRadarrBlock>, _context: Option<ActiveRadarrBlock>,
}
impl<'a, 'b> DeleteMovieHandler<'a, 'b> {
fn build_delete_movie_params(&mut self) -> DeleteMovieParams {
let id = self.app.data.radarr_data.movies.current_selection().id;
let delete_movie_files = self.app.data.radarr_data.delete_movie_files;
let add_list_exclusion = self.app.data.radarr_data.add_list_exclusion;
self.app.data.radarr_data.reset_delete_movie_preferences();
DeleteMovieParams {
id,
delete_movie_files,
add_list_exclusion,
}
}
} }
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DeleteMovieHandler<'a, 'b> { impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DeleteMovieHandler<'a, 'b> {
fn accepts(active_block: &'a ActiveRadarrBlock) -> bool { fn accepts(active_block: ActiveRadarrBlock) -> bool {
DELETE_MOVIE_BLOCKS.contains(active_block) DELETE_MOVIE_BLOCKS.contains(&active_block)
} }
fn with( fn with(
key: &'a Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
active_block: &'a ActiveRadarrBlock, active_block: ActiveRadarrBlock,
_context: &'a Option<ActiveRadarrBlock>, _context: Option<ActiveRadarrBlock>,
) -> Self { ) -> Self {
DeleteMovieHandler { DeleteMovieHandler {
key, key,
@@ -35,7 +51,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DeleteMovieHandler<'
} }
} }
fn get_key(&self) -> &Key { fn get_key(&self) -> Key {
self.key self.key
} }
@@ -44,14 +60,14 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DeleteMovieHandler<'
} }
fn handle_scroll_up(&mut self) { fn handle_scroll_up(&mut self) {
if *self.active_radarr_block == ActiveRadarrBlock::DeleteMoviePrompt { if self.active_radarr_block == ActiveRadarrBlock::DeleteMoviePrompt {
self.app.data.radarr_data.selected_block.previous(); self.app.data.radarr_data.selected_block.up();
} }
} }
fn handle_scroll_down(&mut self) { fn handle_scroll_down(&mut self) {
if *self.active_radarr_block == ActiveRadarrBlock::DeleteMoviePrompt { if self.active_radarr_block == ActiveRadarrBlock::DeleteMoviePrompt {
self.app.data.radarr_data.selected_block.next(); self.app.data.radarr_data.selected_block.down();
} }
} }
@@ -62,17 +78,18 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DeleteMovieHandler<'
fn handle_delete(&mut self) {} fn handle_delete(&mut self) {}
fn handle_left_right_action(&mut self) { fn handle_left_right_action(&mut self) {
if *self.active_radarr_block == ActiveRadarrBlock::DeleteMoviePrompt { if self.active_radarr_block == ActiveRadarrBlock::DeleteMoviePrompt {
handle_prompt_toggle(self.app, self.key); handle_prompt_toggle(self.app, self.key);
} }
} }
fn handle_submit(&mut self) { fn handle_submit(&mut self) {
if self.active_radarr_block == &ActiveRadarrBlock::DeleteMoviePrompt { if self.active_radarr_block == ActiveRadarrBlock::DeleteMoviePrompt {
match self.app.data.radarr_data.selected_block.get_active_block() { match self.app.data.radarr_data.selected_block.get_active_block() {
ActiveRadarrBlock::DeleteMovieConfirmPrompt => { ActiveRadarrBlock::DeleteMovieConfirmPrompt => {
if self.app.data.radarr_data.prompt_confirm { if self.app.data.radarr_data.prompt_confirm {
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DeleteMovie(None)); self.app.data.radarr_data.prompt_confirm_action =
Some(RadarrEvent::DeleteMovie(self.build_delete_movie_params()));
self.app.should_refresh = true; self.app.should_refresh = true;
} else { } else {
self.app.data.radarr_data.reset_delete_movie_preferences(); self.app.data.radarr_data.reset_delete_movie_preferences();
@@ -94,7 +111,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DeleteMovieHandler<'
} }
fn handle_esc(&mut self) { fn handle_esc(&mut self) {
if *self.active_radarr_block == ActiveRadarrBlock::DeleteMoviePrompt { if self.active_radarr_block == ActiveRadarrBlock::DeleteMoviePrompt {
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
self.app.data.radarr_data.reset_delete_movie_preferences(); self.app.data.radarr_data.reset_delete_movie_preferences();
self.app.data.radarr_data.prompt_confirm = false; self.app.data.radarr_data.prompt_confirm = false;
@@ -102,13 +119,14 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DeleteMovieHandler<'
} }
fn handle_char_key_event(&mut self) { fn handle_char_key_event(&mut self) {
if self.active_radarr_block == &ActiveRadarrBlock::DeleteMoviePrompt if self.active_radarr_block == ActiveRadarrBlock::DeleteMoviePrompt
&& self.app.data.radarr_data.selected_block.get_active_block() && self.app.data.radarr_data.selected_block.get_active_block()
== &ActiveRadarrBlock::DeleteMovieConfirmPrompt == ActiveRadarrBlock::DeleteMovieConfirmPrompt
&& *self.key == DEFAULT_KEYBINDINGS.confirm.key && self.key == DEFAULT_KEYBINDINGS.confirm.key
{ {
self.app.data.radarr_data.prompt_confirm = true; self.app.data.radarr_data.prompt_confirm = true;
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DeleteMovie(None)); self.app.data.radarr_data.prompt_confirm_action =
Some(RadarrEvent::DeleteMovie(self.build_delete_movie_params()));
self.app.should_refresh = true; self.app.should_refresh = true;
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
@@ -1,12 +1,15 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use pretty_assertions::assert_eq;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::radarr_handlers::library::delete_movie_handler::DeleteMovieHandler; use crate::handlers::radarr_handlers::library::delete_movie_handler::DeleteMovieHandler;
use crate::handlers::radarr_handlers::radarr_handler_test_utils::utils::movie;
use crate::handlers::KeyEventHandler; use crate::handlers::KeyEventHandler;
use crate::models::radarr_models::DeleteMovieParams;
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, DELETE_MOVIE_BLOCKS}; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, DELETE_MOVIE_BLOCKS};
mod test_handle_scroll_up_and_down { mod test_handle_scroll_up_and_down {
@@ -21,22 +24,20 @@ mod tests {
#[rstest] #[rstest]
fn test_delete_movie_prompt_scroll(#[values(Key::Up, Key::Down)] key: Key) { fn test_delete_movie_prompt_scroll(#[values(Key::Up, Key::Down)] key: Key) {
let mut app = App::default(); let mut app = App::default();
app.data.radarr_data.selected_block = app.data.radarr_data.selected_block = BlockSelectionState::new(DELETE_MOVIE_SELECTION_BLOCKS);
BlockSelectionState::new(&DELETE_MOVIE_SELECTION_BLOCKS); app.data.radarr_data.selected_block.down();
app.data.radarr_data.selected_block.next();
DeleteMovieHandler::with(&key, &mut app, &ActiveRadarrBlock::DeleteMoviePrompt, &None) DeleteMovieHandler::with(key, &mut app, ActiveRadarrBlock::DeleteMoviePrompt, None).handle();
.handle();
if key == Key::Up { if key == Key::Up {
assert_eq!( assert_eq!(
app.data.radarr_data.selected_block.get_active_block(), app.data.radarr_data.selected_block.get_active_block(),
&ActiveRadarrBlock::DeleteMovieToggleDeleteFile ActiveRadarrBlock::DeleteMovieToggleDeleteFile
); );
} else { } else {
assert_eq!( assert_eq!(
app.data.radarr_data.selected_block.get_active_block(), app.data.radarr_data.selected_block.get_active_block(),
&ActiveRadarrBlock::DeleteMovieConfirmPrompt ActiveRadarrBlock::DeleteMovieConfirmPrompt
); );
} }
} }
@@ -47,16 +48,14 @@ mod tests {
) { ) {
let mut app = App::default(); let mut app = App::default();
app.is_loading = true; app.is_loading = true;
app.data.radarr_data.selected_block = app.data.radarr_data.selected_block = BlockSelectionState::new(DELETE_MOVIE_SELECTION_BLOCKS);
BlockSelectionState::new(&DELETE_MOVIE_SELECTION_BLOCKS); app.data.radarr_data.selected_block.down();
app.data.radarr_data.selected_block.next();
DeleteMovieHandler::with(&key, &mut app, &ActiveRadarrBlock::DeleteMoviePrompt, &None) DeleteMovieHandler::with(key, &mut app, ActiveRadarrBlock::DeleteMoviePrompt, None).handle();
.handle();
assert_eq!( assert_eq!(
app.data.radarr_data.selected_block.get_active_block(), app.data.radarr_data.selected_block.get_active_block(),
&ActiveRadarrBlock::DeleteMovieToggleAddListExclusion ActiveRadarrBlock::DeleteMovieToggleAddListExclusion
); );
} }
} }
@@ -70,13 +69,11 @@ mod tests {
fn test_left_right_prompt_toggle(#[values(Key::Left, Key::Right)] key: Key) { fn test_left_right_prompt_toggle(#[values(Key::Left, Key::Right)] key: Key) {
let mut app = App::default(); let mut app = App::default();
DeleteMovieHandler::with(&key, &mut app, &ActiveRadarrBlock::DeleteMoviePrompt, &None) DeleteMovieHandler::with(key, &mut app, ActiveRadarrBlock::DeleteMoviePrompt, None).handle();
.handle();
assert!(app.data.radarr_data.prompt_confirm); assert!(app.data.radarr_data.prompt_confirm);
DeleteMovieHandler::with(&key, &mut app, &ActiveRadarrBlock::DeleteMoviePrompt, &None) DeleteMovieHandler::with(key, &mut app, ActiveRadarrBlock::DeleteMoviePrompt, None).handle();
.handle();
assert!(!app.data.radarr_data.prompt_confirm); assert!(!app.data.radarr_data.prompt_confirm);
} }
@@ -98,25 +95,24 @@ mod tests {
let mut app = App::default(); let mut app = App::default();
app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); app.push_navigation_stack(ActiveRadarrBlock::Movies.into());
app.push_navigation_stack(ActiveRadarrBlock::DeleteMoviePrompt.into()); app.push_navigation_stack(ActiveRadarrBlock::DeleteMoviePrompt.into());
app.data.radarr_data.selected_block = app.data.radarr_data.selected_block = BlockSelectionState::new(DELETE_MOVIE_SELECTION_BLOCKS);
BlockSelectionState::new(&DELETE_MOVIE_SELECTION_BLOCKS);
app app
.data .data
.radarr_data .radarr_data
.selected_block .selected_block
.set_index(DELETE_MOVIE_SELECTION_BLOCKS.len() - 1); .set_index(0, DELETE_MOVIE_SELECTION_BLOCKS.len() - 1);
app.data.radarr_data.delete_movie_files = true; app.data.radarr_data.delete_movie_files = true;
app.data.radarr_data.add_list_exclusion = true; app.data.radarr_data.add_list_exclusion = true;
DeleteMovieHandler::with( DeleteMovieHandler::with(
&SUBMIT_KEY, SUBMIT_KEY,
&mut app, &mut app,
&ActiveRadarrBlock::DeleteMoviePrompt, ActiveRadarrBlock::DeleteMoviePrompt,
&None, None,
) )
.handle(); .handle();
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); assert_eq!(app.get_current_route(), ActiveRadarrBlock::Movies.into());
assert_eq!(app.data.radarr_data.prompt_confirm_action, None); assert_eq!(app.data.radarr_data.prompt_confirm_action, None);
assert!(!app.data.radarr_data.prompt_confirm); assert!(!app.data.radarr_data.prompt_confirm);
assert!(!app.data.radarr_data.delete_movie_files); assert!(!app.data.radarr_data.delete_movie_files);
@@ -126,36 +122,41 @@ mod tests {
#[test] #[test]
fn test_delete_movie_confirm_prompt_prompt_confirmation_submit() { fn test_delete_movie_confirm_prompt_prompt_confirmation_submit() {
let mut app = App::default(); let mut app = App::default();
let expected_delete_movie_params = DeleteMovieParams {
id: 1,
delete_movie_files: true,
add_list_exclusion: true,
};
app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); app.push_navigation_stack(ActiveRadarrBlock::Movies.into());
app.push_navigation_stack(ActiveRadarrBlock::DeleteMoviePrompt.into()); app.push_navigation_stack(ActiveRadarrBlock::DeleteMoviePrompt.into());
app.data.radarr_data.movies.set_items(vec![movie()]);
app.data.radarr_data.prompt_confirm = true; app.data.radarr_data.prompt_confirm = true;
app.data.radarr_data.delete_movie_files = true; app.data.radarr_data.delete_movie_files = true;
app.data.radarr_data.add_list_exclusion = true; app.data.radarr_data.add_list_exclusion = true;
app.data.radarr_data.selected_block = app.data.radarr_data.selected_block = BlockSelectionState::new(DELETE_MOVIE_SELECTION_BLOCKS);
BlockSelectionState::new(&DELETE_MOVIE_SELECTION_BLOCKS);
app app
.data .data
.radarr_data .radarr_data
.selected_block .selected_block
.set_index(DELETE_MOVIE_SELECTION_BLOCKS.len() - 1); .set_index(0, DELETE_MOVIE_SELECTION_BLOCKS.len() - 1);
DeleteMovieHandler::with( DeleteMovieHandler::with(
&SUBMIT_KEY, SUBMIT_KEY,
&mut app, &mut app,
&ActiveRadarrBlock::DeleteMoviePrompt, ActiveRadarrBlock::DeleteMoviePrompt,
&None, None,
) )
.handle(); .handle();
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); assert_eq!(app.get_current_route(), ActiveRadarrBlock::Movies.into());
assert_eq!( assert_eq!(
app.data.radarr_data.prompt_confirm_action, app.data.radarr_data.prompt_confirm_action,
Some(RadarrEvent::DeleteMovie(None)) Some(RadarrEvent::DeleteMovie(expected_delete_movie_params))
); );
assert!(app.should_refresh); assert!(app.should_refresh);
assert!(app.data.radarr_data.prompt_confirm); assert!(app.data.radarr_data.prompt_confirm);
assert!(app.data.radarr_data.delete_movie_files); assert!(!app.data.radarr_data.delete_movie_files);
assert!(app.data.radarr_data.add_list_exclusion); assert!(!app.data.radarr_data.add_list_exclusion);
} }
#[test] #[test]
@@ -169,16 +170,16 @@ mod tests {
app.data.radarr_data.add_list_exclusion = true; app.data.radarr_data.add_list_exclusion = true;
DeleteMovieHandler::with( DeleteMovieHandler::with(
&SUBMIT_KEY, SUBMIT_KEY,
&mut app, &mut app,
&ActiveRadarrBlock::DeleteMoviePrompt, ActiveRadarrBlock::DeleteMoviePrompt,
&None, None,
) )
.handle(); .handle();
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
&ActiveRadarrBlock::DeleteMoviePrompt.into() ActiveRadarrBlock::DeleteMoviePrompt.into()
); );
assert_eq!(app.data.radarr_data.prompt_confirm_action, None); assert_eq!(app.data.radarr_data.prompt_confirm_action, None);
assert!(!app.should_refresh); assert!(!app.should_refresh);
@@ -191,36 +192,36 @@ mod tests {
fn test_delete_movie_toggle_delete_files_submit() { fn test_delete_movie_toggle_delete_files_submit() {
let current_route = ActiveRadarrBlock::DeleteMoviePrompt.into(); let current_route = ActiveRadarrBlock::DeleteMoviePrompt.into();
let mut app = App::default(); let mut app = App::default();
app.data.radarr_data.selected_block = app.data.radarr_data.selected_block = BlockSelectionState::new(DELETE_MOVIE_SELECTION_BLOCKS);
BlockSelectionState::new(&DELETE_MOVIE_SELECTION_BLOCKS);
app.push_navigation_stack(ActiveRadarrBlock::DeleteMoviePrompt.into()); app.push_navigation_stack(ActiveRadarrBlock::DeleteMoviePrompt.into());
DeleteMovieHandler::with( DeleteMovieHandler::with(
&SUBMIT_KEY, SUBMIT_KEY,
&mut app, &mut app,
&ActiveRadarrBlock::DeleteMoviePrompt, ActiveRadarrBlock::DeleteMoviePrompt,
&None, None,
) )
.handle(); .handle();
assert_eq!(app.get_current_route(), &current_route); assert_eq!(app.get_current_route(), current_route);
assert_eq!(app.data.radarr_data.delete_movie_files, true); assert_eq!(app.data.radarr_data.delete_movie_files, true);
DeleteMovieHandler::with( DeleteMovieHandler::with(
&SUBMIT_KEY, SUBMIT_KEY,
&mut app, &mut app,
&ActiveRadarrBlock::DeleteMoviePrompt, ActiveRadarrBlock::DeleteMoviePrompt,
&None, None,
) )
.handle(); .handle();
assert_eq!(app.get_current_route(), &current_route); assert_eq!(app.get_current_route(), current_route);
assert_eq!(app.data.radarr_data.delete_movie_files, false); assert_eq!(app.data.radarr_data.delete_movie_files, false);
} }
} }
mod test_handle_esc { mod test_handle_esc {
use super::*; use super::*;
use pretty_assertions::assert_eq;
use rstest::rstest; use rstest::rstest;
const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key; const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key;
@@ -236,14 +237,14 @@ mod tests {
app.data.radarr_data.add_list_exclusion = true; app.data.radarr_data.add_list_exclusion = true;
DeleteMovieHandler::with( DeleteMovieHandler::with(
&ESC_KEY, ESC_KEY,
&mut app, &mut app,
&ActiveRadarrBlock::DeleteMoviePrompt, ActiveRadarrBlock::DeleteMoviePrompt,
&None, None,
) )
.handle(); .handle();
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); assert_eq!(app.get_current_route(), ActiveRadarrBlock::Movies.into());
assert!(!app.data.radarr_data.prompt_confirm); assert!(!app.data.radarr_data.prompt_confirm);
assert!(!app.data.radarr_data.delete_movie_files); assert!(!app.data.radarr_data.delete_movie_files);
assert!(!app.data.radarr_data.add_list_exclusion); assert!(!app.data.radarr_data.add_list_exclusion);
@@ -257,41 +258,47 @@ mod tests {
}, },
network::radarr_network::RadarrEvent, network::radarr_network::RadarrEvent,
}; };
use pretty_assertions::assert_eq;
use super::*; use super::*;
#[test] #[test]
fn test_delete_movie_confirm_prompt_prompt_confirm() { fn test_delete_movie_confirm_prompt_prompt_confirm() {
let mut app = App::default(); let mut app = App::default();
let expected_delete_movie_params = DeleteMovieParams {
id: 1,
delete_movie_files: true,
add_list_exclusion: true,
};
app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); app.push_navigation_stack(ActiveRadarrBlock::Movies.into());
app.push_navigation_stack(ActiveRadarrBlock::DeleteMoviePrompt.into()); app.push_navigation_stack(ActiveRadarrBlock::DeleteMoviePrompt.into());
app.data.radarr_data.movies.set_items(vec![movie()]);
app.data.radarr_data.delete_movie_files = true; app.data.radarr_data.delete_movie_files = true;
app.data.radarr_data.add_list_exclusion = true; app.data.radarr_data.add_list_exclusion = true;
app.data.radarr_data.selected_block = app.data.radarr_data.selected_block = BlockSelectionState::new(DELETE_MOVIE_SELECTION_BLOCKS);
BlockSelectionState::new(&DELETE_MOVIE_SELECTION_BLOCKS);
app app
.data .data
.radarr_data .radarr_data
.selected_block .selected_block
.set_index(DELETE_MOVIE_SELECTION_BLOCKS.len() - 1); .set_index(0, DELETE_MOVIE_SELECTION_BLOCKS.len() - 1);
DeleteMovieHandler::with( DeleteMovieHandler::with(
&DEFAULT_KEYBINDINGS.confirm.key, DEFAULT_KEYBINDINGS.confirm.key,
&mut app, &mut app,
&ActiveRadarrBlock::DeleteMoviePrompt, ActiveRadarrBlock::DeleteMoviePrompt,
&None, None,
) )
.handle(); .handle();
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); assert_eq!(app.get_current_route(), ActiveRadarrBlock::Movies.into());
assert_eq!( assert_eq!(
app.data.radarr_data.prompt_confirm_action, app.data.radarr_data.prompt_confirm_action,
Some(RadarrEvent::DeleteMovie(None)) Some(RadarrEvent::DeleteMovie(expected_delete_movie_params))
); );
assert!(app.should_refresh); assert!(app.should_refresh);
assert!(app.data.radarr_data.prompt_confirm); assert!(app.data.radarr_data.prompt_confirm);
assert!(app.data.radarr_data.delete_movie_files); assert!(!app.data.radarr_data.delete_movie_files);
assert!(app.data.radarr_data.add_list_exclusion); assert!(!app.data.radarr_data.add_list_exclusion);
} }
} }
@@ -299,23 +306,48 @@ mod tests {
fn test_delete_movie_handler_accepts() { fn test_delete_movie_handler_accepts() {
ActiveRadarrBlock::iter().for_each(|active_radarr_block| { ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
if DELETE_MOVIE_BLOCKS.contains(&active_radarr_block) { if DELETE_MOVIE_BLOCKS.contains(&active_radarr_block) {
assert!(DeleteMovieHandler::accepts(&active_radarr_block)); assert!(DeleteMovieHandler::accepts(active_radarr_block));
} else { } else {
assert!(!DeleteMovieHandler::accepts(&active_radarr_block)); assert!(!DeleteMovieHandler::accepts(active_radarr_block));
} }
}); });
} }
#[test]
fn test_build_delete_movie_params() {
let mut app = App::default();
app.data.radarr_data.movies.set_items(vec![movie()]);
app.data.radarr_data.delete_movie_files = true;
app.data.radarr_data.add_list_exclusion = true;
let expected_delete_movie_params = DeleteMovieParams {
id: 1,
delete_movie_files: true,
add_list_exclusion: true,
};
let delete_movie_params = DeleteMovieHandler::with(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveRadarrBlock::DeleteMoviePrompt,
None,
)
.build_delete_movie_params();
assert_eq!(delete_movie_params, expected_delete_movie_params);
assert!(!app.data.radarr_data.delete_movie_files);
assert!(!app.data.radarr_data.add_list_exclusion);
}
#[test] #[test]
fn test_delete_movie_handler_not_ready_when_loading() { fn test_delete_movie_handler_not_ready_when_loading() {
let mut app = App::default(); let mut app = App::default();
app.is_loading = true; app.is_loading = true;
let handler = DeleteMovieHandler::with( let handler = DeleteMovieHandler::with(
&DEFAULT_KEYBINDINGS.esc.key, DEFAULT_KEYBINDINGS.esc.key,
&mut app, &mut app,
&ActiveRadarrBlock::DeleteMoviePrompt, ActiveRadarrBlock::DeleteMoviePrompt,
&None, None,
); );
assert!(!handler.is_ready()); assert!(!handler.is_ready());
@@ -327,10 +359,10 @@ mod tests {
app.is_loading = false; app.is_loading = false;
let handler = DeleteMovieHandler::with( let handler = DeleteMovieHandler::with(
&DEFAULT_KEYBINDINGS.esc.key, DEFAULT_KEYBINDINGS.esc.key,
&mut app, &mut app,
&ActiveRadarrBlock::DeleteMoviePrompt, ActiveRadarrBlock::DeleteMoviePrompt,
&None, None,
); );
assert!(handler.is_ready()); assert!(handler.is_ready());

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