diff options
Diffstat (limited to 'tool/src/add.rs')
-rw-r--r-- | tool/src/add.rs | 181 |
1 files changed, 181 insertions, 0 deletions
diff --git a/tool/src/add.rs b/tool/src/add.rs new file mode 100644 index 0000000..9cb1180 --- /dev/null +++ b/tool/src/add.rs @@ -0,0 +1,181 @@ +use std::{ + fmt::Display, + path::{Path, PathBuf}, +}; + +use crate::Action; +use anyhow::{anyhow, bail, Context}; +use dialoguer::{theme::ColorfulTheme, Confirm, FuzzySelect, Input, MultiSelect}; +use jellybase::{CONF, SECRETS}; +use jellycommon::{AssetLocation, ImportOptions, ImportSource, TraktKind}; +use jellyimport::trakt::Trakt; +use tokio::{fs::File, io::AsyncWriteExt}; + +pub(crate) 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<usize> = 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::<Vec<_>>(); + + 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 target_dir_index = FuzzySelect::with_theme(&theme) + .items(&directories) + .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(path_to_query).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?; + + let correct = FuzzySelect::with_theme(&theme) + .items(&results) + .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 + }) + }); + + let mut sources = Vec::new(); + sources.push(ImportSource::Trakt { + id: trakt_object.ids.trakt, + kind: trakt_kind, + }); + if let Some(media) = media { + sources.push(ImportSource::Media { + location: AssetLocation::Media(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)) + .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: &PathBuf) -> String { + path.file_stem() + .unwrap() + .to_str() + .unwrap() + .to_string() + .replace("-", " ") + .replace(".", " ") +} + +fn find_folders(base: &Path, path: &Path, out: &mut Vec<PathDisplay>) -> 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()) + } +} |