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:
+139
-84
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
Reference in New Issue
Block a user