From b41d3cf1fac8fefa8d9d109b162a81db4d03a57f Mon Sep 17 00:00:00 2001 From: Felix Bjerhem Aronsson Date: Thu, 23 Mar 2023 09:51:11 +0100 Subject: [PATCH] Added basic sorting options. --- src/arguments.rs | 62 ++++++++++++++++++++ src/arr/mod.rs | 2 +- src/config.rs | 7 ++- src/main.rs | 144 ++++++++++++++++++++++++++++++++++++++++------ src/media_item.rs | 40 +------------ 5 files changed, 197 insertions(+), 58 deletions(-) create mode 100644 src/arguments.rs diff --git a/src/arguments.rs b/src/arguments.rs new file mode 100644 index 0000000..1eeaaff --- /dev/null +++ b/src/arguments.rs @@ -0,0 +1,62 @@ +use color_eyre::Result; +use itertools::Itertools; +use once_cell::sync::OnceCell; +use std::env; + +use crate::{Order, SortingOption}; + +static INSTANCE: OnceCell = OnceCell::new(); + +#[derive(Debug)] +pub struct Arguments { + pub sorting: Option, +} + +impl Arguments { + pub fn get_args() -> &'static Arguments { + INSTANCE.get().expect("Arguments have not been initialised") + } + + pub fn read_args() -> Result<()> { + if let Some(_) = INSTANCE.get() { + return Ok(()); + } + + let mut args = env::args().collect_vec(); + + let args = Arguments { + sorting: Self::read_sort(&mut args), + }; + + INSTANCE + .set(args) + .expect("Arguments have already been initialized..."); + Ok(()) + } + + fn read_sort(args: &mut Vec) -> Option { + for (i, arg) in args.iter_mut().enumerate() { + match arg.as_str() { + "-n" => { + args.swap_remove(i); + return Some(SortingOption::Name(Order::Asc)); + } + "-nd" => { + args.swap_remove(i); + return Some(SortingOption::Name(Order::Desc)); + } + "-s" => { + args.swap_remove(i); + return Some(SortingOption::Size(Order::Desc)); + } + "-sa" => { + args.swap_remove(i); + return Some(SortingOption::Size(Order::Asc)); + } + _ => continue, + } + } + + None + } +} diff --git a/src/arr/mod.rs b/src/arr/mod.rs index 209a411..3f899f0 100644 --- a/src/arr/mod.rs +++ b/src/arr/mod.rs @@ -171,7 +171,7 @@ impl Display for TvData { 0 => "unknown".to_string(), count => count.to_string() }.yellow(), - &format!("{}%", self.percent_of_episodes_on_disk).blue(), + &format!("{:.2}%", self.percent_of_episodes_on_disk).blue(), ) } } diff --git a/src/config.rs b/src/config.rs index 23404fe..5c97e9a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -3,6 +3,7 @@ use once_cell::sync::OnceCell; use serde::Deserialize; use std::fs; +static INSTANCE: OnceCell = OnceCell::new(); #[derive(Debug, Deserialize)] pub struct Config { #[serde(default = "default_items_shown")] @@ -44,14 +45,16 @@ pub struct Radarr { pub url: String, } -static INSTANCE: OnceCell = OnceCell::new(); - impl Config { pub fn global() -> &'static Config { INSTANCE.get().expect("Config has not been initialized.") } pub fn read_conf() -> Result<()> { + if let Some(_) = INSTANCE.get() { + return Ok(()); + } + let reader = fs::File::open("config.yaml")?; let mut conf: Config = serde_yaml::from_reader(reader)?; diff --git a/src/main.rs b/src/main.rs index 49c7ebe..097e19d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +mod arguments; mod arr; mod config; mod media_item; @@ -8,11 +9,15 @@ mod tautulli; mod utils; use color_eyre::{eyre::eyre, Report, Result}; +use futures::future; +use itertools::Itertools; +use overseerr::MediaRequest; use std::{io, process::Command}; +use arguments::Arguments; use config::Config; use dialoguer::MultiSelect; -use media_item::{gather_requests_data, CompleteMediaItem}; +use media_item::{CompleteMediaItem, MediaItem}; use crate::utils::human_file_size; @@ -22,13 +27,15 @@ async fn main() -> Result<()> { read_and_validate_config()?; - let (mut requests, errs) = gather_requests_data().await?; + Arguments::read_args()?; - show_requests_result(&requests, errs)?; + let mut requests = gather_requests_data().await?; + + show_requests_result(&requests)?; clear_screen()?; - let chosen = choose_items_to_delete(&requests)?; + let chosen = choose_items_to_delete(&mut requests)?; delete_chosen_items(&mut requests, &chosen).await?; @@ -48,20 +55,42 @@ fn read_and_validate_config() -> Result<()> { Ok(()) } -fn show_requests_result(requests: &Vec, errs: Vec) -> Result<()> { - show_potential_request_errors(errs)?; +async fn gather_requests_data() -> Result> { + println!("Gathering all required data from your services.\nDepending on the amount of requests and your connection speed, this could take a while..."); - if requests.len() == 0 { - println!("You do not seem to have any valid requests, with data available."); - println!("Are you sure all your requests are available and downloaded? Or some data was unable to be acquired from other services."); - println!("Either try again later, or look over your requests."); + let requests = MediaRequest::get_all().await?; - println!(); - wait(None)?; - std::process::exit(0); - } + let futures = requests + .into_iter() + .map(MediaItem::from_request) + .filter(|i| i.is_available() && i.has_manager_active()) + .map(|item| { + tokio::spawn(async move { + let item = item.into_complete().await?; - Ok(()) + Ok::(item) + }) + }); + + let mut errors: Vec = Vec::new(); + + let complete_items = future::try_join_all(futures) + .await? + .into_iter() + .filter_map(|f| match f { + Ok(item) => Some(item), + Err(err) => { + errors.push(err); + None + } + }) + .unique_by(|item| item.title.clone()) + .sorted_by(|item1, item2| item1.title.cmp(&item2.title)) + .collect(); + + show_potential_request_errors(errors)?; + + Ok(complete_items) } fn show_potential_request_errors(errs: Vec) -> Result<()> { @@ -98,7 +127,23 @@ fn show_potential_request_errors(errs: Vec) -> Result<()> { Ok(()) } -fn choose_items_to_delete(requests: &Vec) -> Result> { +fn show_requests_result(requests: &Vec) -> Result<()> { + if requests.len() == 0 { + println!("You do not seem to have any valid requests, with data available."); + println!("Are you sure all your requests are available and downloaded? Or some data was unable to be acquired from other services."); + println!("Either try again later, or look over your requests."); + + println!(); + wait(None)?; + std::process::exit(0); + } + + Ok(()) +} + +fn choose_items_to_delete(requests: &mut Vec) -> Result> { + choose_sorting(requests)?; + clear_screen()?; let items_to_show = Config::global().items_shown; @@ -120,6 +165,73 @@ fn choose_items_to_delete(requests: &Vec) -> Result Self { + Self::Name(Order::Asc) + } +} + +fn choose_sorting(requests: &mut Vec) -> Result<()> { + clear_screen()?; + + let args = Arguments::get_args(); + + let sort = match args.sorting { + Some(sort) => sort, + None => choose_sorting_dialogue()?, + }; + + match sort { + SortingOption::Name(Order::Desc) => return Ok(requests.reverse()), + SortingOption::Name(Order::Asc) => return Ok(()), + SortingOption::Size(Order::Asc) => { + return Ok(requests.sort_by_key(|req| req.get_disk_size())) + } + SortingOption::Size(Order::Desc) => { + return Ok( + requests.sort_by(|req1, req2| req2.get_disk_size().cmp(&req1.get_disk_size())) + ) + } + } +} + +fn choose_sorting_dialogue() -> Result { + loop { + println!("Choose sorting method:"); + println!("Name - Ascending: n (or just enter, it's the default)"); + println!("Name - Descending: nd"); + println!("Size - Descending: s"); + println!("Size - Ascending: sa"); + + let input = get_user_input()?; + + match input + .strip_suffix("\r\n") + .or(input.strip_suffix("\n")) + .unwrap_or(&input) + { + "nd" => return Ok(SortingOption::Name(Order::Desc)), + "n" => return Ok(SortingOption::Name(Order::Asc)), + "sa" => return Ok(SortingOption::Size(Order::Asc)), + "s" => return Ok(SortingOption::Size(Order::Desc)), + "" => return Ok(SortingOption::default()), + _ => (), + }; + } +} + fn verify_chosen(requests: &Vec, chosen: &Vec) -> Result<()> { let total_size: String = human_file_size( chosen diff --git a/src/media_item.rs b/src/media_item.rs index bd4407f..c8b7572 100644 --- a/src/media_item.rs +++ b/src/media_item.rs @@ -1,6 +1,4 @@ -use color_eyre::{eyre::eyre, owo_colors::OwoColorize, Report, Result}; -use futures::future::{self}; -use itertools::Itertools; +use color_eyre::{eyre::eyre, owo_colors::OwoColorize, Result}; use std::fmt::{Debug, Display}; use tokio::try_join; @@ -120,42 +118,6 @@ impl CompleteMediaItem { } } -pub async fn gather_requests_data() -> Result<(Vec, Vec)> { - println!("Gathering all required data from your services.\nDepending on the amount of requests and your connection speed,this could take a while..."); - - let requests = MediaRequest::get_all().await?; - - let futures = requests - .into_iter() - .map(MediaItem::from_request) - .filter(|i| i.is_available() && i.has_manager_active()) - .map(|item| { - tokio::spawn(async move { - let item = item.into_complete().await?; - - Ok::(item) - }) - }); - - let mut errors: Vec = Vec::new(); - - let complete_items = future::try_join_all(futures) - .await? - .into_iter() - .filter_map(|f| match f { - Ok(item) => Some(item), - Err(err) => { - errors.push(err); - None - } - }) - .unique_by(|item| item.title.clone()) - .sorted_by(|item1, item2| item1.title.cmp(&item2.title)) - .collect(); - - Ok((complete_items, errors)) -} - impl Display for CompleteMediaItem { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(