Did a lot of things in this one: Cleaned up a bit of leftover unused code from yesterday; deprecated the use of drain() on HorizontallyScrollableText; Refactored the uses of search and filter to be wrapped in Options; Fixed a race condition when closing the Add Movie modals in rapid succession; upgraded to ratatui 0.22.0; Fixed a bug in attempting to close an empty root folder; fixed a bug in attempting to close an empty filter; fixed a bug in attempting to close an empty search; fixed a bug in attempting to close an empty filter without resetting the view; fixed a bug in attempting to delete a movie after dynamically added one and updating the main library table

This commit is contained in:
2023-08-08 10:50:07 -06:00
parent 2d624e2648
commit 77fd9e621f
28 changed files with 1151 additions and 352 deletions
+139 -84
View File
@@ -1,7 +1,8 @@
use anyhow::anyhow;
use std::fmt::Debug;
use indoc::formatdoc;
use log::{debug, info};
use log::{debug, info, warn};
use serde::Serialize;
use serde_json::{json, Number, Value};
use urlencoding::encode;
@@ -184,7 +185,7 @@ impl<'a, 'b> Network<'a, 'b> {
.collection_movies
.current_selection()
.clone();
(tmdb_id, title.text.clone())
(tmdb_id, title.text)
} else {
let AddMovieSearchResult { tmdb_id, title, .. } = app
.data
@@ -192,7 +193,7 @@ impl<'a, 'b> Network<'a, 'b> {
.add_searched_movies
.current_selection()
.clone();
(tmdb_id, title.text.clone())
(tmdb_id, title.text)
}
} else {
let AddMovieSearchResult { tmdb_id, title, .. } = app
@@ -201,7 +202,7 @@ impl<'a, 'b> Network<'a, 'b> {
.add_searched_movies
.current_selection()
.clone();
(tmdb_id, title.text.clone())
(tmdb_id, title.text)
};
let quality_profile = quality_profile_list.current_selection();
let quality_profile_id = *app
@@ -252,8 +253,20 @@ impl<'a, 'b> Network<'a, 'b> {
async fn add_root_folder(&mut self) {
info!("Adding new root folder to Radarr");
let body = AddRootFolderBody {
path: self.app.lock().await.data.radarr_data.edit_path.drain(),
let body = {
let mut app = self.app.lock().await;
let path = app
.data
.radarr_data
.edit_root_folder
.as_ref()
.unwrap()
.text
.clone();
app.data.radarr_data.edit_root_folder = None;
AddRootFolderBody { path }
};
debug!("Add root folder body: {:?}", body);
@@ -356,13 +369,13 @@ impl<'a, 'b> Network<'a, 'b> {
}
async fn delete_movie(&mut self) {
let movie_id = self.extract_movie_id().await;
let (movie_id, tmdb_id) = self.extract_movie_id().await;
let delete_files = self.app.lock().await.data.radarr_data.delete_movie_files;
let add_import_exclusion = self.app.lock().await.data.radarr_data.add_list_exclusion;
info!(
"Deleting Radarr movie with id: {} with deleteFiles={} and addImportExclusion={}",
movie_id, delete_files, add_import_exclusion
"Deleting Radarr movie with tmdb_id {} and Radarr id: {} with deleteFiles={} and addImportExclusion={}",
tmdb_id, movie_id, delete_files, add_import_exclusion
);
let request_props = self
@@ -477,9 +490,11 @@ impl<'a, 'b> Network<'a, 'b> {
)
.await;
let mut response = String::new();
self
.handle_request::<(), Value>(request_props, |detailed_collection_body, mut app| {
app.response = detailed_collection_body.to_string()
.handle_request::<(), Value>(request_props, |detailed_collection_body, _| {
response = detailed_collection_body.to_string()
})
.await;
@@ -487,7 +502,6 @@ impl<'a, 'b> Network<'a, 'b> {
let body = {
let mut app = self.app.lock().await;
let response = app.response.drain(..).collect::<String>();
let mut detailed_collection_body: Value = serde_json::from_str(&response).unwrap();
let EditCollectionModal {
path,
@@ -550,8 +564,9 @@ impl<'a, 'b> Network<'a, 'b> {
async fn edit_movie(&mut self) {
info!("Editing Radarr movie");
info!("Fetching movie details");
let movie_id = self.extract_movie_id().await;
let (movie_id, tmdb_id) = self.extract_movie_id().await;
info!("Fetching movie details for movie with TMDB ID: {tmdb_id}");
let request_props = self
.radarr_request_props_from(
format!("{}/{}", RadarrEvent::GetMovieDetails.resource(), movie_id).as_str(),
@@ -560,9 +575,11 @@ impl<'a, 'b> Network<'a, 'b> {
)
.await;
let mut response = String::new();
self
.handle_request::<(), Value>(request_props, |detailed_movie_body, mut app| {
app.response = detailed_movie_body.to_string()
.handle_request::<(), Value>(request_props, |detailed_movie_body, _| {
response = detailed_movie_body.to_string()
})
.await;
@@ -583,7 +600,6 @@ impl<'a, 'b> Network<'a, 'b> {
.clone();
let tag_ids_vec = self.extract_and_add_tag_ids_vec(tags).await;
let mut app = self.app.lock().await;
let response = app.response.drain(..).collect::<String>();
let mut detailed_movie_body: Value = serde_json::from_str(&response).unwrap();
let EditMovieModal {
@@ -819,7 +835,9 @@ impl<'a, 'b> Network<'a, 'b> {
async fn get_movie_details(&mut self) {
info!("Fetching Radarr movie details");
let movie_id = self.extract_movie_id().await;
let (movie_id, tmdb_id) = self.extract_movie_id().await;
info!("Fetching movie details for movie with TMDB ID: {tmdb_id}");
let request_props = self
.radarr_request_props_from(
format!("{}/{}", RadarrEvent::GetMovieDetails.resource(), movie_id).as_str(),
@@ -1057,8 +1075,11 @@ impl<'a, 'b> Network<'a, 'b> {
}
async fn get_releases(&mut self) {
let movie_id = self.extract_movie_id().await;
info!("Fetching releases for movie with id: {}", movie_id);
let (movie_id, tmdb_id) = self.extract_movie_id().await;
info!(
"Fetching releases for movie with TMDB id {} and with Radarr id: {}",
tmdb_id, movie_id
);
let request_props = self
.radarr_request_props_from(
@@ -1249,34 +1270,56 @@ impl<'a, 'b> Network<'a, 'b> {
async fn search_movie(&mut self) {
info!("Searching for specific Radarr movie");
let search = self
.app
.lock()
.await
.data
.radarr_data
.search
.clone()
.ok_or(anyhow!("Encountered a race condition"));
let search_string = self.app.lock().await.data.radarr_data.search.text.clone();
let request_props = self
.radarr_request_props_from(
format!(
"{}?term={}",
RadarrEvent::SearchNewMovie.resource(),
encode(&search_string)
)
.as_str(),
RequestMethod::Get,
None::<()>,
)
.await;
match search {
Ok(search_string) => {
let request_props = self
.radarr_request_props_from(
format!(
"{}?term={}",
RadarrEvent::SearchNewMovie.resource(),
encode(&search_string.text)
)
.as_str(),
RequestMethod::Get,
None::<()>,
)
.await;
self
.handle_request::<(), Vec<AddMovieSearchResult>>(request_props, |movie_vec, mut app| {
if movie_vec.is_empty() {
app.pop_and_push_navigation_stack(ActiveRadarrBlock::AddMovieEmptySearchResults.into());
} else {
app
.data
.radarr_data
.add_searched_movies
.set_items(movie_vec);
}
})
.await;
self
.handle_request::<(), Vec<AddMovieSearchResult>>(request_props, |movie_vec, mut app| {
if movie_vec.is_empty() {
app.pop_and_push_navigation_stack(
ActiveRadarrBlock::AddMovieEmptySearchResults.into(),
);
} else {
app
.data
.radarr_data
.add_searched_movies
.set_items(movie_vec);
}
})
.await;
}
Err(e) => {
warn!(
"Encountered a race condition: {}\n \
This is most likely caused by the user trying to navigate between modals rapidly. \
Ignoring search request.",
e
);
}
}
}
async fn start_task(&mut self) {
@@ -1309,8 +1352,11 @@ impl<'a, 'b> Network<'a, 'b> {
}
async fn trigger_automatic_search(&mut self) {
let movie_id = self.extract_movie_id().await;
info!("Searching indexers for movie with id: {}", movie_id);
let (movie_id, tmdb_id) = self.extract_movie_id().await;
info!(
"Searching indexers for movie with TMDB id {} and with Radarr id: {}",
tmdb_id, movie_id
);
let body = MovieCommandBody {
name: "MoviesSearch".to_owned(),
movie_ids: vec![movie_id],
@@ -1350,8 +1396,11 @@ impl<'a, 'b> Network<'a, 'b> {
}
async fn update_and_scan(&mut self) {
let movie_id = self.extract_movie_id().await;
info!("Updating and scanning movie with id: {}", movie_id);
let (movie_id, tmdb_id) = self.extract_movie_id().await;
info!(
"Updating and scanning movie with TMDB id {} and with Radarr id: {}",
tmdb_id, movie_id
);
let body = MovieCommandBody {
name: "RefreshMovie".to_owned(),
movie_ids: vec![movie_id],
@@ -1493,40 +1542,46 @@ impl<'a, 'b> Network<'a, 'b> {
.collect()
}
async fn extract_movie_id(&mut self) -> u64 {
if !self
.app
.lock()
.await
.data
.radarr_data
.filtered_movies
.items
.is_empty()
{
self
.app
.lock()
.await
.data
.radarr_data
.filtered_movies
.current_selection()
.id
.as_u64()
.unwrap()
async fn extract_movie_id(&mut self) -> (u64, u64) {
let app = self.app.lock().await;
if !app.data.radarr_data.filtered_movies.items.is_empty() {
(
app
.data
.radarr_data
.filtered_movies
.current_selection()
.id
.as_u64()
.unwrap(),
app
.data
.radarr_data
.filtered_movies
.current_selection()
.tmdb_id
.as_u64()
.unwrap(),
)
} else {
self
.app
.lock()
.await
.data
.radarr_data
.movies
.current_selection()
.id
.as_u64()
.unwrap()
(
app
.data
.radarr_data
.movies
.current_selection()
.id
.as_u64()
.unwrap(),
app
.data
.radarr_data
.movies
.current_selection()
.tmdb_id
.as_u64()
.unwrap(),
)
}
}
@@ -1568,7 +1623,7 @@ impl<'a, 'b> Network<'a, 'b> {
}
async fn append_movie_id_param(&mut self, resource: &str) -> String {
let movie_id = self.extract_movie_id().await;
let (movie_id, _) = self.extract_movie_id().await;
format!("{}?movieId={}", resource, movie_id)
}
}
+59 -10
View File
@@ -357,7 +357,7 @@ mod test {
&resource,
)
.await;
app_arc.lock().await.data.radarr_data.search = "test term".to_owned().into();
app_arc.lock().await.data.radarr_data.search = Some("test term".into());
let mut network = Network::new(&app_arc, CancellationToken::new());
network
@@ -413,7 +413,7 @@ mod test {
);
let (async_server, app_arc, _server) =
mock_radarr_api(RequestMethod::Get, None, Some(json!([])), &resource).await;
app_arc.lock().await.data.radarr_data.search = "test term".to_owned().into();
app_arc.lock().await.data.radarr_data.search = Some("test term".into());
let mut network = Network::new(&app_arc, CancellationToken::new());
network
@@ -435,6 +435,56 @@ mod test {
);
}
#[tokio::test]
async fn test_handle_search_new_movie_event_no_panic_on_race_condition() {
let resource = format!(
"{}?term=test%20term",
RadarrEvent::SearchNewMovie.resource()
);
let mut server = Server::new_async().await;
let mut async_server = server
.mock(
&RequestMethod::Get.to_string().to_uppercase(),
format!("/api/v3{}", resource).as_str(),
)
.match_header("X-Api-Key", "test1234");
async_server = async_server.expect_at_most(0).create_async().await;
let host = server.host_with_port().split(':').collect::<Vec<&str>>()[0].to_owned();
let port = Some(
server.host_with_port().split(':').collect::<Vec<&str>>()[1]
.parse()
.unwrap(),
);
let mut app = App::default();
let radarr_config = RadarrConfig {
host,
port,
api_token: "test1234".to_owned(),
};
app.config.radarr = radarr_config;
let app_arc = Arc::new(Mutex::new(app));
let mut network = Network::new(&app_arc, CancellationToken::new());
network
.handle_radarr_event(RadarrEvent::SearchNewMovie)
.await;
async_server.assert_async().await;
assert!(app_arc
.lock()
.await
.data
.radarr_data
.add_searched_movies
.items
.is_empty());
assert_eq!(
app_arc.lock().await.get_current_route(),
&ActiveRadarrBlock::Movies.into()
);
}
#[tokio::test]
async fn test_handle_trigger_automatic_search_event() {
let (async_server, app_arc, _server) = mock_radarr_api(
@@ -1574,7 +1624,7 @@ mod test {
)
.await;
app_arc.lock().await.data.radarr_data.edit_path = HorizontallyScrollableText::from("/nfs/test");
app_arc.lock().await.data.radarr_data.edit_root_folder = Some("/nfs/test".into());
let mut network = Network::new(&app_arc, CancellationToken::new());
network
@@ -1587,9 +1637,8 @@ mod test {
.await
.data
.radarr_data
.edit_path
.text
.is_empty());
.edit_root_folder
.is_none());
}
#[tokio::test]
@@ -1651,7 +1700,6 @@ mod test {
async_edit_server.assert_async().await;
let app = app_arc.lock().await;
assert!(app.response.is_empty());
assert!(app.data.radarr_data.edit_movie_modal.is_none());
assert!(app.data.radarr_data.movie_details.items.is_empty());
}
@@ -1747,7 +1795,6 @@ mod test {
async_edit_server.assert_async().await;
let app = app_arc.lock().await;
assert!(app.response.is_empty());
assert!(app.data.radarr_data.edit_collection_modal.is_none());
assert!(app.data.radarr_data.movie_details.items.is_empty());
}
@@ -1846,11 +1893,12 @@ mod test {
.movies
.set_items(vec![Movie {
id: Number::from(1),
tmdb_id: Number::from(2),
..Movie::default()
}]);
let mut network = Network::new(&app_arc, CancellationToken::new());
assert_eq!(network.extract_movie_id().await, 1);
assert_eq!(network.extract_movie_id().await, (1, 2));
}
#[tokio::test]
@@ -1864,11 +1912,12 @@ mod test {
.filtered_movies
.set_items(vec![Movie {
id: Number::from(1),
tmdb_id: Number::from(2),
..Movie::default()
}]);
let mut network = Network::new(&app_arc, CancellationToken::new());
assert_eq!(network.extract_movie_id().await, 1);
assert_eq!(network.extract_movie_id().await, (1, 2));
}
#[tokio::test]