Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 48ad17c6f1 | |||
| 3cd15f34cd | |||
| 53ca14e64d | |||
| 0d8803d35d | |||
| 8c90221a81 | |||
| a708f71d57 | |||
| 2a13f74a2b |
@@ -0,0 +1,7 @@
|
||||
[tool.commitizen]
|
||||
name = "cz_conventional_commits"
|
||||
tag_format = "v$version"
|
||||
version_scheme = "semver"
|
||||
version_provider = "cargo"
|
||||
update_changelog_on_bump = true
|
||||
major_version_zero = true
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
- name: Run release-plz
|
||||
uses: MarcoIeni/release-plz-action@v0.5
|
||||
with:
|
||||
bump: major
|
||||
command: release --bump-major
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
- name: Run release-plz
|
||||
uses: MarcoIeni/release-plz-action@v0.5
|
||||
with:
|
||||
bump: minor
|
||||
command: release --bump-minor
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
- name: Run release-plz
|
||||
uses: MarcoIeni/release-plz-action@v0.5
|
||||
with:
|
||||
bump: patch
|
||||
command: release --bump-patch
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
repos:
|
||||
- hooks:
|
||||
- id: commitizen
|
||||
- id: commitizen-branch
|
||||
stages:
|
||||
- pre-push
|
||||
repo: https://github.com/commitizen-tools/commitizen
|
||||
rev: v3.30.0
|
||||
@@ -1,6 +1,7 @@
|
||||
# Contributing
|
||||
Contributors are very welcome! **No contribution is too small and all contributions are valued.**
|
||||
|
||||
## Rust
|
||||
You'll need to have the stable Rust toolchain installed in order to develop Managarr.
|
||||
|
||||
The Rust toolchain (stable) can be installed via rustup using the following command:
|
||||
@@ -11,6 +12,37 @@ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
|
||||
This will install `rustup`, `rustc` and `cargo`. For more information, refer to the [official Rust installation documentation](https://www.rust-lang.org/tools/install).
|
||||
|
||||
## Commitizen
|
||||
[Commitizen](https://github.com/commitizen-tools/commitizen?tab=readme-ov-file) is a nifty tool that helps us write better commit messages. It ensures that our
|
||||
commits have a consistent style and makes it easier to generate CHANGELOGS. Additionally,
|
||||
Commitizen is used to run pre-commit checks to enforce style constraints.
|
||||
|
||||
To install `commitizen` and the `pre-commit` prerequisite, run the following command:
|
||||
|
||||
```shell
|
||||
python3 -m pip install commitizen pre-commit
|
||||
```
|
||||
|
||||
### Commitizen Quick Guide
|
||||
To see an example commit to get an idea for the Commitizen style, run:
|
||||
|
||||
```shell
|
||||
cz example
|
||||
```
|
||||
|
||||
To see the allowed types of commits and their descriptions, run:
|
||||
|
||||
```shell
|
||||
cz info
|
||||
```
|
||||
|
||||
If you'd like to create a commit using Commitizen with an interactive prompt to help you get
|
||||
comfortable with the style, use:
|
||||
|
||||
```shell
|
||||
cz commit
|
||||
```
|
||||
|
||||
## Setup workspace
|
||||
|
||||
1. Clone this repo
|
||||
|
||||
Generated
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "managarr",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
+35
-5
@@ -87,7 +87,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_reset_cancellation_token() {
|
||||
let mut app = App::default();
|
||||
let mut app = App {
|
||||
is_loading: true,
|
||||
should_refresh: false,
|
||||
..App::default()
|
||||
};
|
||||
app.cancellation_token.cancel();
|
||||
|
||||
assert!(app.cancellation_token.is_cancelled());
|
||||
@@ -96,6 +100,8 @@ mod tests {
|
||||
|
||||
assert!(!app.cancellation_token.is_cancelled());
|
||||
assert!(!new_token.is_cancelled());
|
||||
assert!(!app.is_loading);
|
||||
assert!(app.should_refresh);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -145,6 +151,29 @@ mod tests {
|
||||
assert_eq!(app.error.text, test_string);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_dispatch_network_event() {
|
||||
let (sync_network_tx, mut sync_network_rx) = mpsc::channel::<NetworkEvent>(500);
|
||||
|
||||
let mut app = App {
|
||||
tick_until_poll: 2,
|
||||
network_tx: Some(sync_network_tx),
|
||||
..App::default()
|
||||
};
|
||||
|
||||
assert_eq!(app.tick_count, 0);
|
||||
|
||||
app
|
||||
.dispatch_network_event(RadarrEvent::GetStatus.into())
|
||||
.await;
|
||||
|
||||
assert_eq!(
|
||||
sync_network_rx.recv().await.unwrap(),
|
||||
RadarrEvent::GetStatus.into()
|
||||
);
|
||||
assert_eq!(app.tick_count, 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_on_tick_first_render() {
|
||||
let (sync_network_tx, mut sync_network_rx) = mpsc::channel::<NetworkEvent>(500);
|
||||
@@ -158,6 +187,7 @@ mod tests {
|
||||
assert_eq!(app.tick_count, 0);
|
||||
|
||||
app.on_tick(true).await;
|
||||
|
||||
assert_eq!(
|
||||
sync_network_rx.recv().await.unwrap(),
|
||||
RadarrEvent::GetQualityProfiles.into()
|
||||
@@ -170,6 +200,10 @@ mod tests {
|
||||
sync_network_rx.recv().await.unwrap(),
|
||||
RadarrEvent::GetRootFolders.into()
|
||||
);
|
||||
assert_eq!(
|
||||
sync_network_rx.recv().await.unwrap(),
|
||||
RadarrEvent::GetDownloads.into()
|
||||
);
|
||||
assert_eq!(
|
||||
sync_network_rx.recv().await.unwrap(),
|
||||
RadarrEvent::GetOverview.into()
|
||||
@@ -182,10 +216,6 @@ mod tests {
|
||||
sync_network_rx.recv().await.unwrap(),
|
||||
RadarrEvent::GetMovies.into()
|
||||
);
|
||||
assert_eq!(
|
||||
sync_network_rx.recv().await.unwrap(),
|
||||
RadarrEvent::GetDownloads.into()
|
||||
);
|
||||
assert!(!app.is_routing);
|
||||
assert!(!app.should_refresh);
|
||||
assert_eq!(app.tick_count, 1);
|
||||
|
||||
+6
-1
@@ -56,7 +56,10 @@ impl<'a> App<'a> {
|
||||
pub async fn dispatch_network_event(&mut self, action: NetworkEvent) {
|
||||
debug!("Dispatching network event: {action:?}");
|
||||
|
||||
self.is_loading = true;
|
||||
if !self.should_refresh {
|
||||
self.is_loading = true;
|
||||
}
|
||||
|
||||
if let Some(network_tx) = &self.network_tx {
|
||||
if let Err(e) = network_tx.send(action).await {
|
||||
self.is_loading = false;
|
||||
@@ -113,6 +116,8 @@ impl<'a> App<'a> {
|
||||
|
||||
pub fn reset_cancellation_token(&mut self) -> CancellationToken {
|
||||
self.cancellation_token = CancellationToken::new();
|
||||
self.should_refresh = true;
|
||||
self.is_loading = false;
|
||||
|
||||
self.cancellation_token.clone()
|
||||
}
|
||||
|
||||
+12
-19
@@ -142,35 +142,22 @@ impl<'a> App<'a> {
|
||||
is_first_render: bool,
|
||||
) {
|
||||
if is_first_render {
|
||||
self
|
||||
.dispatch_network_event(RadarrEvent::GetQualityProfiles.into())
|
||||
.await;
|
||||
self
|
||||
.dispatch_network_event(RadarrEvent::GetTags.into())
|
||||
.await;
|
||||
self
|
||||
.dispatch_network_event(RadarrEvent::GetRootFolders.into())
|
||||
.await;
|
||||
self
|
||||
.dispatch_network_event(RadarrEvent::GetOverview.into())
|
||||
.await;
|
||||
self
|
||||
.dispatch_network_event(RadarrEvent::GetStatus.into())
|
||||
.await;
|
||||
self.refresh_metadata().await;
|
||||
self.dispatch_by_radarr_block(&active_radarr_block).await;
|
||||
}
|
||||
|
||||
if self.should_refresh {
|
||||
self.dispatch_by_radarr_block(&active_radarr_block).await;
|
||||
self.refresh_metadata().await;
|
||||
}
|
||||
|
||||
if self.is_routing {
|
||||
if self.is_loading && !self.should_refresh {
|
||||
if !self.should_refresh {
|
||||
self.cancellation_token.cancel();
|
||||
} else {
|
||||
self.dispatch_by_radarr_block(&active_radarr_block).await;
|
||||
self.refresh_metadata().await;
|
||||
}
|
||||
|
||||
self.dispatch_by_radarr_block(&active_radarr_block).await;
|
||||
self.refresh_metadata().await;
|
||||
}
|
||||
|
||||
if self.tick_count % self.tick_until_poll == 0 {
|
||||
@@ -191,6 +178,12 @@ impl<'a> App<'a> {
|
||||
self
|
||||
.dispatch_network_event(RadarrEvent::GetDownloads.into())
|
||||
.await;
|
||||
self
|
||||
.dispatch_network_event(RadarrEvent::GetOverview.into())
|
||||
.await;
|
||||
self
|
||||
.dispatch_network_event(RadarrEvent::GetStatus.into())
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn populate_movie_collection_table(&mut self) {
|
||||
|
||||
@@ -508,6 +508,14 @@ mod tests {
|
||||
sync_network_rx.recv().await.unwrap(),
|
||||
RadarrEvent::GetDownloads.into()
|
||||
);
|
||||
assert_eq!(
|
||||
sync_network_rx.recv().await.unwrap(),
|
||||
RadarrEvent::GetOverview.into()
|
||||
);
|
||||
assert_eq!(
|
||||
sync_network_rx.recv().await.unwrap(),
|
||||
RadarrEvent::GetStatus.into()
|
||||
);
|
||||
assert!(app.is_loading);
|
||||
}
|
||||
|
||||
@@ -529,6 +537,10 @@ mod tests {
|
||||
sync_network_rx.recv().await.unwrap(),
|
||||
RadarrEvent::GetRootFolders.into()
|
||||
);
|
||||
assert_eq!(
|
||||
sync_network_rx.recv().await.unwrap(),
|
||||
RadarrEvent::GetDownloads.into()
|
||||
);
|
||||
assert_eq!(
|
||||
sync_network_rx.recv().await.unwrap(),
|
||||
RadarrEvent::GetOverview.into()
|
||||
@@ -537,10 +549,6 @@ mod tests {
|
||||
sync_network_rx.recv().await.unwrap(),
|
||||
RadarrEvent::GetStatus.into()
|
||||
);
|
||||
assert_eq!(
|
||||
sync_network_rx.recv().await.unwrap(),
|
||||
RadarrEvent::GetDownloads.into()
|
||||
);
|
||||
assert!(app.is_loading);
|
||||
assert!(!app.data.radarr_data.prompt_confirm);
|
||||
}
|
||||
@@ -549,6 +557,7 @@ mod tests {
|
||||
async fn test_radarr_on_tick_routing() {
|
||||
let (mut app, mut sync_network_rx) = construct_app_unit();
|
||||
app.is_routing = true;
|
||||
app.should_refresh = true;
|
||||
|
||||
app
|
||||
.radarr_on_tick(ActiveRadarrBlock::Downloads, false)
|
||||
@@ -574,43 +583,19 @@ mod tests {
|
||||
sync_network_rx.recv().await.unwrap(),
|
||||
RadarrEvent::GetDownloads.into()
|
||||
);
|
||||
assert!(app.is_loading);
|
||||
assert!(!app.data.radarr_data.prompt_confirm);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_radarr_on_tick_routing_while_long_request_is_running_should_cancel_request() {
|
||||
let (mut app, mut sync_network_rx) = construct_app_unit();
|
||||
let (mut app, _) = construct_app_unit();
|
||||
app.is_routing = true;
|
||||
app.is_loading = true;
|
||||
app.should_refresh = false;
|
||||
|
||||
app
|
||||
.radarr_on_tick(ActiveRadarrBlock::Downloads, false)
|
||||
.await;
|
||||
|
||||
assert_eq!(
|
||||
sync_network_rx.recv().await.unwrap(),
|
||||
RadarrEvent::GetDownloads.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!(
|
||||
sync_network_rx.recv().await.unwrap(),
|
||||
RadarrEvent::GetRootFolders.into()
|
||||
);
|
||||
assert_eq!(
|
||||
sync_network_rx.recv().await.unwrap(),
|
||||
RadarrEvent::GetDownloads.into()
|
||||
);
|
||||
assert!(app.is_loading);
|
||||
assert!(!app.data.radarr_data.prompt_confirm);
|
||||
assert!(app.cancellation_token.is_cancelled());
|
||||
}
|
||||
|
||||
@@ -627,7 +612,6 @@ mod tests {
|
||||
sync_network_rx.recv().await.unwrap(),
|
||||
RadarrEvent::GetDownloads.into()
|
||||
);
|
||||
assert!(app.is_loading);
|
||||
assert!(app.should_refresh);
|
||||
assert!(!app.data.radarr_data.prompt_confirm);
|
||||
}
|
||||
|
||||
@@ -47,18 +47,28 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for MovieDetailsHandler<
|
||||
}
|
||||
|
||||
fn is_ready(&self) -> bool {
|
||||
let movie_details_modal_is_ready =
|
||||
if let Some(movie_details_modal) = &self.app.data.radarr_data.movie_details_modal {
|
||||
!movie_details_modal.movie_details.is_empty()
|
||||
|| !movie_details_modal.movie_history.is_empty()
|
||||
|| !movie_details_modal.movie_cast.is_empty()
|
||||
|| !movie_details_modal.movie_crew.is_empty()
|
||||
|| !movie_details_modal.movie_releases.is_empty()
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
!self.app.is_loading && movie_details_modal_is_ready
|
||||
if let Some(movie_details_modal) = &self.app.data.radarr_data.movie_details_modal {
|
||||
match self.active_radarr_block {
|
||||
ActiveRadarrBlock::MovieDetails => {
|
||||
!self.app.is_loading && !movie_details_modal.movie_details.is_empty()
|
||||
}
|
||||
ActiveRadarrBlock::MovieHistory => {
|
||||
!self.app.is_loading && !movie_details_modal.movie_history.is_empty()
|
||||
}
|
||||
ActiveRadarrBlock::Cast => {
|
||||
!self.app.is_loading && !movie_details_modal.movie_cast.is_empty()
|
||||
}
|
||||
ActiveRadarrBlock::Crew => {
|
||||
!self.app.is_loading && !movie_details_modal.movie_crew.is_empty()
|
||||
}
|
||||
ActiveRadarrBlock::ManualSearch => {
|
||||
!self.app.is_loading && !movie_details_modal.movie_releases.is_empty()
|
||||
}
|
||||
_ => !self.app.is_loading,
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_scroll_up(&mut self) {
|
||||
|
||||
@@ -3,6 +3,7 @@ mod tests {
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use pretty_assertions::assert_str_eq;
|
||||
use rstest::rstest;
|
||||
use serde_json::Number;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
@@ -1245,10 +1246,12 @@ mod tests {
|
||||
#[test]
|
||||
fn test_manual_search_submit() {
|
||||
let mut app = App::default();
|
||||
app.data.radarr_data.movie_details_modal = Some(MovieDetailsModal {
|
||||
let mut modal = MovieDetailsModal {
|
||||
movie_details: ScrollableText::with_string("test".to_owned()),
|
||||
..MovieDetailsModal::default()
|
||||
});
|
||||
};
|
||||
modal.movie_releases.set_items(vec![Release::default()]);
|
||||
app.data.radarr_data.movie_details_modal = Some(modal);
|
||||
app.push_navigation_stack(ActiveRadarrBlock::ManualSearch.into());
|
||||
|
||||
MovieDetailsHandler::with(
|
||||
@@ -1486,10 +1489,17 @@ mod tests {
|
||||
active_radarr_block: ActiveRadarrBlock,
|
||||
) {
|
||||
let mut app = App::default();
|
||||
app.data.radarr_data.movie_details_modal = Some(MovieDetailsModal {
|
||||
movie_details: ScrollableText::with_string("test".to_owned()),
|
||||
let mut modal = MovieDetailsModal {
|
||||
movie_details: ScrollableText::with_string("Test".to_owned()),
|
||||
..MovieDetailsModal::default()
|
||||
});
|
||||
};
|
||||
modal
|
||||
.movie_history
|
||||
.set_items(vec![MovieHistoryItem::default()]);
|
||||
modal.movie_cast.set_items(vec![Credit::default()]);
|
||||
modal.movie_crew.set_items(vec![Credit::default()]);
|
||||
modal.movie_releases.set_items(vec![Release::default()]);
|
||||
app.data.radarr_data.movie_details_modal = Some(modal);
|
||||
|
||||
MovieDetailsHandler::with(
|
||||
&DEFAULT_KEYBINDINGS.search.key,
|
||||
@@ -1539,10 +1549,9 @@ mod tests {
|
||||
#[test]
|
||||
fn test_sort_key() {
|
||||
let mut app = App::default();
|
||||
app.data.radarr_data.movie_details_modal = Some(MovieDetailsModal {
|
||||
movie_details: ScrollableText::with_string("test".to_owned()),
|
||||
..MovieDetailsModal::default()
|
||||
});
|
||||
let mut modal = MovieDetailsModal::default();
|
||||
modal.movie_releases.set_items(release_vec());
|
||||
app.data.radarr_data.movie_details_modal = Some(modal);
|
||||
|
||||
MovieDetailsHandler::with(
|
||||
&DEFAULT_KEYBINDINGS.sort.key,
|
||||
@@ -1670,10 +1679,17 @@ mod tests {
|
||||
active_radarr_block: ActiveRadarrBlock,
|
||||
) {
|
||||
let mut app = App::default();
|
||||
app.data.radarr_data.movie_details_modal = Some(MovieDetailsModal {
|
||||
movie_details: ScrollableText::with_string("test".to_owned()),
|
||||
let mut modal = MovieDetailsModal {
|
||||
movie_details: ScrollableText::with_string("Test".to_owned()),
|
||||
..MovieDetailsModal::default()
|
||||
});
|
||||
};
|
||||
modal
|
||||
.movie_history
|
||||
.set_items(vec![MovieHistoryItem::default()]);
|
||||
modal.movie_cast.set_items(vec![Credit::default()]);
|
||||
modal.movie_crew.set_items(vec![Credit::default()]);
|
||||
modal.movie_releases.set_items(vec![Release::default()]);
|
||||
app.data.radarr_data.movie_details_modal = Some(modal);
|
||||
|
||||
MovieDetailsHandler::with(
|
||||
&DEFAULT_KEYBINDINGS.update.key,
|
||||
@@ -1733,10 +1749,17 @@ mod tests {
|
||||
active_radarr_block: ActiveRadarrBlock,
|
||||
) {
|
||||
let mut app = App::default();
|
||||
app.data.radarr_data.movie_details_modal = Some(MovieDetailsModal {
|
||||
movie_details: ScrollableText::with_string("test".to_owned()),
|
||||
let mut modal = MovieDetailsModal {
|
||||
movie_details: ScrollableText::with_string("Test".to_owned()),
|
||||
..MovieDetailsModal::default()
|
||||
});
|
||||
};
|
||||
modal
|
||||
.movie_history
|
||||
.set_items(vec![MovieHistoryItem::default()]);
|
||||
modal.movie_cast.set_items(vec![Credit::default()]);
|
||||
modal.movie_crew.set_items(vec![Credit::default()]);
|
||||
modal.movie_releases.set_items(vec![Release::default()]);
|
||||
app.data.radarr_data.movie_details_modal = Some(modal);
|
||||
|
||||
MovieDetailsHandler::with(
|
||||
&DEFAULT_KEYBINDINGS.refresh.key,
|
||||
@@ -1994,15 +2017,37 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_movie_details_handler_is_not_ready_when_loading() {
|
||||
#[rstest]
|
||||
fn test_movie_details_handler_is_not_ready_when_loading(
|
||||
#[values(
|
||||
ActiveRadarrBlock::MovieDetails,
|
||||
ActiveRadarrBlock::MovieHistory,
|
||||
ActiveRadarrBlock::FileInfo,
|
||||
ActiveRadarrBlock::Cast,
|
||||
ActiveRadarrBlock::Crew,
|
||||
ActiveRadarrBlock::ManualSearch,
|
||||
ActiveRadarrBlock::ManualSearch
|
||||
)]
|
||||
movie_details_block: ActiveRadarrBlock,
|
||||
) {
|
||||
let mut app = App::default();
|
||||
app.is_loading = true;
|
||||
let mut modal = MovieDetailsModal {
|
||||
movie_details: ScrollableText::with_string("Test".to_owned()),
|
||||
..MovieDetailsModal::default()
|
||||
};
|
||||
modal
|
||||
.movie_history
|
||||
.set_items(vec![MovieHistoryItem::default()]);
|
||||
modal.movie_cast.set_items(vec![Credit::default()]);
|
||||
modal.movie_crew.set_items(vec![Credit::default()]);
|
||||
modal.movie_releases.set_items(vec![Release::default()]);
|
||||
app.data.radarr_data.movie_details_modal = Some(modal);
|
||||
|
||||
let handler = MovieDetailsHandler::with(
|
||||
&DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
&ActiveRadarrBlock::MovieDetails,
|
||||
&movie_details_block,
|
||||
&None,
|
||||
);
|
||||
|
||||
|
||||
+21
-5
@@ -6,6 +6,7 @@ use std::panic::PanicHookInfo;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use std::{io, panic, process};
|
||||
|
||||
use anyhow::anyhow;
|
||||
@@ -20,11 +21,12 @@ use crossterm::execute;
|
||||
use crossterm::terminal::{
|
||||
disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
|
||||
};
|
||||
use log::error;
|
||||
use log::{error, warn};
|
||||
use network::NetworkTrait;
|
||||
use ratatui::backend::CrosstermBackend;
|
||||
use ratatui::Terminal;
|
||||
use reqwest::{Certificate, Client};
|
||||
use tokio::select;
|
||||
use tokio::sync::mpsc::Receiver;
|
||||
use tokio::sync::{mpsc, Mutex};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
@@ -144,9 +146,20 @@ async fn start_networking(
|
||||
) {
|
||||
let mut network = Network::new(app, cancellation_token, client);
|
||||
|
||||
while let Some(network_event) = network_rx.recv().await {
|
||||
if let Err(e) = network.handle_network_event(network_event).await {
|
||||
error!("Encountered an error handling network event: {e:?}");
|
||||
loop {
|
||||
select! {
|
||||
Some(network_event) = network_rx.recv() => {
|
||||
if let Err(e) = network.handle_network_event(network_event).await {
|
||||
error!("Encountered an error handling network event: {e:?}");
|
||||
}
|
||||
}
|
||||
_ = network.cancellation_token.cancelled() => {
|
||||
warn!("Clearing network channel");
|
||||
while network_rx.try_recv().is_ok() {
|
||||
// Discard the message
|
||||
}
|
||||
network.reset_cancellation_token().await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -228,7 +241,10 @@ fn load_config(path: &str) -> Result<AppConfig> {
|
||||
}
|
||||
|
||||
fn build_network_client(config: &AppConfig) -> Client {
|
||||
let mut client_builder = Client::builder();
|
||||
let mut client_builder = Client::builder()
|
||||
.pool_max_idle_per_host(10)
|
||||
.http2_keep_alive_interval(Duration::from_secs(5))
|
||||
.tcp_keepalive(Duration::from_secs(5));
|
||||
|
||||
if let Some(ref cert_path) = config.radarr.ssl_cert_path {
|
||||
let cert = create_cert(cert_path, "Radarr");
|
||||
|
||||
+5
-4
@@ -40,7 +40,7 @@ pub trait NetworkTrait {
|
||||
#[derive(Clone)]
|
||||
pub struct Network<'a, 'b> {
|
||||
client: Client,
|
||||
cancellation_token: CancellationToken,
|
||||
pub cancellation_token: CancellationToken,
|
||||
pub app: &'a Arc<Mutex<App<'b>>>,
|
||||
}
|
||||
|
||||
@@ -74,6 +74,10 @@ impl<'a, 'b> Network<'a, 'b> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn reset_cancellation_token(&mut self) {
|
||||
self.cancellation_token = self.app.lock().await.reset_cancellation_token();
|
||||
}
|
||||
|
||||
async fn handle_request<B, R>(
|
||||
&mut self,
|
||||
request_props: RequestProps<B>,
|
||||
@@ -89,9 +93,6 @@ impl<'a, 'b> Network<'a, 'b> {
|
||||
select! {
|
||||
_ = self.cancellation_token.cancelled() => {
|
||||
warn!("Received Cancel request. Cancelling request to: {request_uri}");
|
||||
let mut app = self.app.lock().await;
|
||||
self.cancellation_token = app.reset_cancellation_token();
|
||||
app.is_loading = false;
|
||||
Ok(R::default())
|
||||
}
|
||||
resp = self.call_api(request_props).await.send() => {
|
||||
|
||||
@@ -181,11 +181,31 @@ mod tests {
|
||||
|
||||
assert!(!async_server.matched_async().await);
|
||||
assert!(app_arc.lock().await.error.text.is_empty());
|
||||
assert!(!network.cancellation_token.is_cancelled());
|
||||
assert!(resp.is_ok());
|
||||
assert_eq!(resp.unwrap(), Test::default());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_reset_cancellation_token() {
|
||||
let cancellation_token = CancellationToken::new();
|
||||
let (tx, _) = mpsc::channel::<NetworkEvent>(500);
|
||||
let app_arc = Arc::new(Mutex::new(App::new(
|
||||
tx,
|
||||
AppConfig::default(),
|
||||
cancellation_token.clone(),
|
||||
)));
|
||||
app_arc.lock().await.should_refresh = false;
|
||||
app_arc.lock().await.is_loading = true;
|
||||
let mut network = Network::new(&app_arc, cancellation_token, Client::new());
|
||||
network.cancellation_token.cancel();
|
||||
|
||||
network.reset_cancellation_token().await;
|
||||
|
||||
assert!(!network.cancellation_token.is_cancelled());
|
||||
assert!(app_arc.lock().await.should_refresh);
|
||||
assert!(!app_arc.lock().await.is_loading);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_request_get_invalid_body() {
|
||||
let mut server = Server::new_async().await;
|
||||
|
||||
@@ -166,6 +166,7 @@ fn draw_file_info(f: &mut Frame<'_>, app: &App<'_>, area: Rect) {
|
||||
|
||||
fn draw_movie_details(f: &mut Frame<'_>, app: &App<'_>, area: Rect) {
|
||||
let block = layout_block_top_border();
|
||||
let unknown_download_status = "Status: Unknown".to_owned();
|
||||
|
||||
match app.data.radarr_data.movie_details_modal.as_ref() {
|
||||
Some(movie_details_modal) if !app.is_loading => {
|
||||
@@ -182,7 +183,7 @@ fn draw_movie_details(f: &mut Frame<'_>, app: &App<'_>, area: Rect) {
|
||||
.items
|
||||
.iter()
|
||||
.find(|&line| line.starts_with("Status: "))
|
||||
.unwrap()
|
||||
.unwrap_or(&unknown_download_status)
|
||||
.split(": ")
|
||||
.collect::<Vec<&str>>()[1];
|
||||
let text = Text::from(
|
||||
@@ -285,81 +286,88 @@ fn draw_movie_history(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
}
|
||||
|
||||
fn draw_movie_cast(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
let cast_row_mapping = |cast_member: &Credit| {
|
||||
let Credit {
|
||||
person_name,
|
||||
character,
|
||||
..
|
||||
} = cast_member;
|
||||
match app.data.radarr_data.movie_details_modal.as_mut() {
|
||||
Some(movie_details_modal) if !app.is_loading => {
|
||||
let cast_row_mapping = |cast_member: &Credit| {
|
||||
let Credit {
|
||||
person_name,
|
||||
character,
|
||||
..
|
||||
} = cast_member;
|
||||
|
||||
Row::new(vec![
|
||||
Cell::from(person_name.to_owned()),
|
||||
Cell::from(character.clone().unwrap_or_default()),
|
||||
])
|
||||
.success()
|
||||
};
|
||||
let content = Some(
|
||||
&mut app
|
||||
.data
|
||||
.radarr_data
|
||||
.movie_details_modal
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.movie_cast,
|
||||
);
|
||||
let help_footer = app
|
||||
.data
|
||||
.radarr_data
|
||||
.movie_info_tabs
|
||||
.get_active_tab_contextual_help();
|
||||
let cast_table = ManagarrTable::new(content, cast_row_mapping)
|
||||
.block(layout_block_top_border())
|
||||
.footer(help_footer)
|
||||
.loading(app.is_loading)
|
||||
.headers(["Cast Member", "Character"])
|
||||
.constraints([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)]);
|
||||
Row::new(vec![
|
||||
Cell::from(person_name.to_owned()),
|
||||
Cell::from(character.clone().unwrap_or_default()),
|
||||
])
|
||||
.success()
|
||||
};
|
||||
let content = Some(&mut movie_details_modal.movie_cast);
|
||||
let help_footer = app
|
||||
.data
|
||||
.radarr_data
|
||||
.movie_info_tabs
|
||||
.get_active_tab_contextual_help();
|
||||
let cast_table = ManagarrTable::new(content, cast_row_mapping)
|
||||
.block(layout_block_top_border())
|
||||
.footer(help_footer)
|
||||
.loading(app.is_loading)
|
||||
.headers(["Cast Member", "Character"])
|
||||
.constraints([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)]);
|
||||
|
||||
f.render_widget(cast_table, area);
|
||||
f.render_widget(cast_table, area);
|
||||
}
|
||||
_ => f.render_widget(
|
||||
LoadingBlock::new(
|
||||
app.is_loading || app.data.radarr_data.movie_details_modal.is_none(),
|
||||
layout_block_top_border(),
|
||||
),
|
||||
area,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_movie_crew(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
let crew_row_mapping = |crew_member: &Credit| {
|
||||
let Credit {
|
||||
person_name,
|
||||
job,
|
||||
department,
|
||||
..
|
||||
} = crew_member;
|
||||
match app.data.radarr_data.movie_details_modal.as_mut() {
|
||||
Some(movie_details_modal) if !app.is_loading => {
|
||||
let crew_row_mapping = |crew_member: &Credit| {
|
||||
let Credit {
|
||||
person_name,
|
||||
job,
|
||||
department,
|
||||
..
|
||||
} = crew_member;
|
||||
|
||||
Row::new(vec![
|
||||
Cell::from(person_name.to_owned()),
|
||||
Cell::from(job.clone().unwrap_or_default()),
|
||||
Cell::from(department.clone().unwrap_or_default()),
|
||||
])
|
||||
.success()
|
||||
};
|
||||
let content = Some(
|
||||
&mut app
|
||||
.data
|
||||
.radarr_data
|
||||
.movie_details_modal
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.movie_crew,
|
||||
);
|
||||
let help_footer = app
|
||||
.data
|
||||
.radarr_data
|
||||
.movie_info_tabs
|
||||
.get_active_tab_contextual_help();
|
||||
let crew_table = ManagarrTable::new(content, crew_row_mapping)
|
||||
.block(layout_block_top_border())
|
||||
.loading(app.is_loading)
|
||||
.headers(["Crew Member", "Job", "Department"])
|
||||
.constraints(iter::repeat(Constraint::Ratio(1, 3)).take(3))
|
||||
.footer(help_footer);
|
||||
Row::new(vec![
|
||||
Cell::from(person_name.to_owned()),
|
||||
Cell::from(job.clone().unwrap_or_default()),
|
||||
Cell::from(department.clone().unwrap_or_default()),
|
||||
])
|
||||
.success()
|
||||
};
|
||||
let content = Some(&mut movie_details_modal.movie_crew);
|
||||
let help_footer = app
|
||||
.data
|
||||
.radarr_data
|
||||
.movie_info_tabs
|
||||
.get_active_tab_contextual_help();
|
||||
let crew_table = ManagarrTable::new(content, crew_row_mapping)
|
||||
.block(layout_block_top_border())
|
||||
.loading(app.is_loading)
|
||||
.headers(["Crew Member", "Job", "Department"])
|
||||
.constraints(iter::repeat(Constraint::Ratio(1, 3)).take(3))
|
||||
.footer(help_footer);
|
||||
|
||||
f.render_widget(crew_table, area);
|
||||
f.render_widget(crew_table, area);
|
||||
}
|
||||
|
||||
_ => f.render_widget(
|
||||
LoadingBlock::new(
|
||||
app.is_loading || app.data.radarr_data.movie_details_modal.is_none(),
|
||||
layout_block_top_border(),
|
||||
),
|
||||
area,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_movie_releases(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
|
||||
Reference in New Issue
Block a user