use crate::cli::Action; use anyhow::{anyhow, bail, Context}; use dialoguer::{theme::ColorfulTheme, FuzzySelect, Input, MultiSelect}; use jellybase::{CONF, SECRETS}; use jellycommon::TraktKind; use jellyimport::trakt::Trakt; use log::warn; use std::{ fmt::Display, path::{Path, PathBuf}, }; pub async fn add(action: Action) -> anyhow::Result<()> { match action { Action::Add { id, media, library_path, } => { let theme = ColorfulTheme::default(); let possible_kinds = [ TraktKind::Movie, TraktKind::Season, TraktKind::Show, TraktKind::Episode, ]; let trakt_kind: Vec = MultiSelect::with_theme(&theme) .with_prompt("Media Kind") .items(&possible_kinds) .defaults(&[true, false, false, false]) .interact() .unwrap(); let search_kinds = trakt_kind .iter() .map(|&i| possible_kinds[i]) .collect::>(); let library_path = if let Some(library_path) = library_path { library_path } else { let mut directories = Vec::new(); find_folders(&CONF.library_path, &PathBuf::new(), &mut directories) .context("listing library directories")?; let mut default = 0; for k in possible_kinds { match k { TraktKind::Movie => { if let Some(i) = directories .iter() .position(|d| d.0.to_str().unwrap().contains("movies")) { default = i }; } TraktKind::Show => { if let Some(i) = directories .iter() .position(|d| d.0.to_str().unwrap().contains("shows")) { default = i }; } _ => (), } } let target_dir_index = FuzzySelect::with_theme(&theme) .items(&directories) .default(default) .with_prompt("Library Path") .interact() .unwrap(); directories[target_dir_index].0.clone() }; let (last_search, trakt_object, trakt_kind) = loop { let name: String = Input::with_theme(&theme) .with_prompt("Search by title") .default(media.as_ref().map(|p| path_to_query(p)).unwrap_or_default()) .interact_text() .unwrap(); let trakt = Trakt::new( SECRETS .api .trakt .as_ref() .ok_or(anyhow!("no trakt api key configured"))?, ); let results = trakt.search(&search_kinds, &name, false).await?; if results.is_empty() { warn!("no search results"); continue; } let correct = FuzzySelect::with_theme(&theme) .items(&results) .default(0) .with_prompt("Metadata Source") .interact_opt() .unwrap(); if let Some(o) = correct { break (name, results[o].inner.inner().to_owned(), results[o].r#type); } }; let id = id.unwrap_or_else(|| { trakt_object.ids.slug.unwrap_or_else(|| { let o: String = Input::with_theme(&theme) .with_prompt("Node ID") .validate_with(validate_id) .default(make_id(&last_search)) .interact_text() .unwrap(); o }) }); // TODO let _ = id; let _ = library_path; let _ = trakt_kind; // let mut sources = Vec::new(); // sources.push(ImportSource::Trakt { // id: trakt_object.ids.trakt.unwrap(), // kind: trakt_kind, // }); // if let Some(media) = media { // sources.push(ImportSource::Media { // path: media, // ignore_metadata: true, // ignore_attachments: false, // ignore_chapters: false, // }) // } // let impo = ImportOptions { id, sources }; // let ypath = CONF // .library_path // .join(library_path) // .join(&impo.id) // .with_extension("yaml"); // if Confirm::with_theme(&theme) // .with_prompt(format!("Write {:?}?", ypath)) // .default(true) // .interact() // .unwrap() // { // File::create(ypath) // .await? // .write_all(serde_yaml::to_string(&impo)?.as_bytes()) // .await?; // } Ok(()) } _ => unreachable!(), } } fn validate_id(s: &String) -> anyhow::Result<()> { if &make_id(s) == s { Ok(()) } else { bail!("invalid id") } } fn make_id(s: &str) -> String { let mut out = String::new(); for s in s.chars() { match s { 'a'..='z' | '0'..='9' => out.push(s), 'A'..='Z' => out.push(s.to_ascii_lowercase()), '-' | ' ' | '_' | ':' => out.push('-'), _ => (), } } out } fn path_to_query(path: &Path) -> String { path.file_stem() .unwrap() .to_str() .unwrap() .to_string() .replace("-", " ") .replace(".", " ") } fn find_folders(base: &Path, path: &Path, out: &mut Vec) -> anyhow::Result<()> { out.push(PathDisplay(path.to_owned())); for entry in base.join(path).read_dir()? { let entry = entry?; let child_path = path.join(entry.file_name()); if entry.path().is_dir() { find_folders(base, &child_path, out)?; } } Ok(()) } pub struct PathDisplay(PathBuf); impl Display for PathDisplay { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str("/")?; f.write_str(self.0.to_str().unwrap()) } }