From be1544ecf343d8805269ea5174f84a6a8d9efd36 Mon Sep 17 00:00:00 2001 From: Karl Skomski Date: Sun, 26 Jul 2020 19:27:35 +0200 Subject: [PATCH] implement new config format json --- Cargo.lock | 1 + Cargo.toml | 1 + src/download.rs | 4 +- src/soundboards.rs | 227 +++++++++++++++++++++++++++++++++++---------- src/telegram.rs | 147 ++++++++++++----------------- 5 files changed, 243 insertions(+), 137 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a21a7ea..42e3b3a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3486,6 +3486,7 @@ dependencies = [ "reqwest", "rust-embed", "serde", + "serde_json", "strum", "strum_macros", "tgbot", diff --git a/Cargo.toml b/Cargo.toml index 0148c6d..4f8998a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,6 +51,7 @@ regex = "1" reqwest = {version = "0.10", features = ["blocking"]} rust-embed = {version = "5.6.0", features = ["interpolate-folder-path"]} serde = {version = "1.0", features = ["derive"]} +serde_json = "1.0" strum = "0.18" strum_macros = "0.18" thiserror = "1" diff --git a/src/download.rs b/src/download.rs index abe845a..ac15c8a 100644 --- a/src/download.rs +++ b/src/download.rs @@ -57,7 +57,7 @@ pub fn local_path_for_sound_config_exists(sound: &soundboards::Sound) -> Result< } } soundboards::Source::Local { path } => { - Ok(Some(resolve_local_sound_path(sound, path.to_path_buf())?)) + Ok(Some(resolve_local_sound_path(sound, PathBuf::from(path))?)) } soundboards::Source::Youtube { id } => { let string_hash = utils::calculate_hash(&id).to_string(); @@ -100,7 +100,7 @@ pub fn get_local_path_from_sound_config(sound: &soundboards::Sound) -> Result resolve_local_sound_path(sound, path.to_path_buf()), + soundboards::Source::Local { path } => resolve_local_sound_path(sound, PathBuf::from(path)), soundboards::Source::Youtube { id } => { let string_hash = utils::calculate_hash(&id).to_string(); let mut file_path = std::env::temp_dir(); diff --git a/src/soundboards.rs b/src/soundboards.rs index 6f2019e..4868247 100644 --- a/src/soundboards.rs +++ b/src/soundboards.rs @@ -48,6 +48,20 @@ pub fn reload_soundboards_from_disk() -> Result<()> { Ok(()) } +// pub fn save_soundboards_to_disk() -> Result<()> { +// for soundboard in get_soundboards().values() { +// soundboard.clone().save_to_disk(); +// } +// Ok(()) +// } + +pub fn update_soundboards(soundboard: Soundboard) -> Result<()> { + let mut cloned_map = (**GLOBAL_SOUNDBOARD_MAP.read()).clone(); + cloned_map.insert(soundboard.id, soundboard); + *GLOBAL_SOUNDBOARD_MAP.write() = std::sync::Arc::new(cloned_map); + Ok(()) +} + /// Returns the soundboard with the specified id pub fn get_soundboard( id: Ulid, @@ -93,21 +107,29 @@ pub struct Soundboard { id: SoundboardId, path: PathBuf, - last_hash: u64, + last_hash: Option, } impl Soundboard { - pub fn new(name: &str, path: &str) -> Self { - Self { + pub fn new(name: &str) -> Result { + let mut path = helpers::get_soundboards_path().unwrap(); + path.push(name); + path = path.with_extension("json"); + + if path.exists() { + return Err(anyhow!("soundboard path already exists {}", name)); + } + + Ok(Self { name: name.to_owned(), hotkey: None, position: None, sounds: SoundMap::default(), sound_positions: Vec::new(), id: Ulid::new(), - path: PathBuf::from(path), - last_hash: 0, - } + path, + last_hash: None, + }) } fn from_config(soundboard_path: &Path, config: SoundboardConfig) -> Result { @@ -129,7 +151,7 @@ impl Soundboard { } }; Ok(Self { - last_hash: hash, + last_hash: Some(hash), name: config.name, position: config.position, hotkey, @@ -143,33 +165,77 @@ impl Soundboard { /// Save soundboard to disk /// /// fails if soundboard on disk was modified from our last load from disk - // pub fn save_to_disk(&mut self) -> Result<()> { - // let mut soundboard_map: SoundboardMap = (**GLOBAL_SOUNDBOARD_MAP.read()).clone(); - // let mut config = SoundboardConfig::new(self.name.as_str()); - - // if let Some(val) = soundboard_map.get_mut(&self.id) { - // if self.last_hash == 0 { - // panic!("should never be 0 for old soundboard"); - // } - // save_soundboard_config(self.path.as_path(), &config, Some(self.last_hash))?; - // *val = self.clone(); - // *GLOBAL_SOUNDBOARD_MAP.write() = std::sync::Arc::new(soundboard_map); - // } else { - // if self.last_hash != 0 { - // panic!("should never be not 0 for new soundboard"); - // } - // save_soundboard_config(self.path.as_path(), &config, None)?; - // soundboard_map.insert(self.id, self.clone()); - // *GLOBAL_SOUNDBOARD_MAP.write() = std::sync::Arc::new(soundboard_map); - // } - // self.last_hash = utils::calculate_hash(&config); - // Ok(()) - // } + pub fn save_to_disk(&mut self) -> Result<()> { + let mut config = SoundboardConfig::new(self.name.as_str()); + + if let Some(hotkey) = self.get_hotkey() { + config.hotkey = Some(hotkey.to_string()); + } + + config.position = *self.get_position(); + config.sounds = Some( + self.iter() + .map(|s| SoundConfig::from(s)) + .collect::>(), + ); + + save_soundboard_config(self.path.as_path(), &config, self.last_hash)?; + + self.last_hash = Some(utils::calculate_hash(&config)); + Ok(()) + } /// Add sound to soundboard - pub fn insert_sound(&mut self, sound: Sound) -> Option { + pub fn add_sound(&mut self, sound: Sound) -> Result<()> { + if let Source::Local { path } = sound.get_source() { + let path = PathBuf::from(path); + if path.is_absolute() && !path.exists() { + return Err(anyhow!( + "local sound file does not exist: {}", + path.display() + )); + } else { + let mut sound_path = self.get_sounds_path()?; + sound_path.push(path); + if !sound_path.exists() { + return Err(anyhow!( + "local sound file does not exist: {}", + sound_path.display() + )); + } + } + } self.sound_positions.push(sound.id); - self.sounds.insert(sound.id, sound) + self.sounds.insert(sound.id, sound); + Ok(()) + } + + pub fn add_sound_with_file_path(&mut self, sound: Sound, source_path: &Path) -> Result<()> { + if let Source::Local { path } = sound.get_source() { + let mut sound_path = self.get_sounds_path()?; + sound_path.push(path); + if sound_path.exists() { + return Err(anyhow!( + "local sound file already exists: {}", + sound_path.display() + )); + } + let new_sounds_path = get_soundboard_sound_directory(self.get_path())?; + + if !new_sounds_path.exists() { + std::fs::create_dir(new_sounds_path)?; + } + std::fs::copy(source_path, &sound_path).with_context(|| { + format!( + "cant copy file from {} to {}", + source_path.display(), + &sound_path.display() + ) + })?; + self.add_sound(sound) + } else { + Err(anyhow!("not a local source sound")) + } } pub fn get_id(&self) -> &Ulid { @@ -207,7 +273,7 @@ impl Soundboard { &self.sound_positions } - pub fn iter<'a>(&self) -> SoundIterator { + pub fn iter(&self) -> SoundIterator { SoundIterator::new(&self) } } @@ -341,10 +407,14 @@ impl Sound { #[derive(Debug, Deserialize, Clone, Serialize, Eq, PartialEq, Hash, Default)] struct SoundboardConfig { pub name: String, + #[serde(skip_serializing_if = "Option::is_none")] pub hotkey: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub position: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub disabled: Option, #[serde(rename = "sound")] + #[serde(skip_serializing_if = "Option::is_none")] pub sounds: Option>, } @@ -361,18 +431,22 @@ impl SoundboardConfig { struct SoundConfig { pub name: String, pub source: Source, + #[serde(skip_serializing_if = "Option::is_none")] pub hotkey: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub start: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub end: Option, } #[derive(Debug, Deserialize, Clone, Serialize, PartialEq, Eq, Hash)] pub enum Source { #[serde(rename = "local")] - Local { path: PathBuf }, + Local { path: String }, #[serde(rename = "http")] Http { url: String, + #[serde(skip_serializing_if = "Option::is_none")] headers: Option>, }, #[serde(rename = "youtube")] @@ -397,6 +471,23 @@ impl SoundConfig { end: None, } } + + pub fn from(sound: &Sound) -> Self { + let hotkey = { + if let Some(hotkey) = sound.get_hotkey() { + Some(hotkey.to_string()) + } else { + None + } + }; + Self { + name: sound.get_name().to_string(), + source: sound.get_source().clone(), + hotkey, + start: sound.get_start(), + end: sound.get_end(), + } + } } impl PartialEq for SoundConfig { @@ -439,13 +530,9 @@ fn load_and_parse_soundboards() -> Result { continue; } let path = entry.unwrap().path(); - let extension: &str = path - .extension() - .unwrap_or_default() - .to_str() - .unwrap_or_default(); + let extension = path.extension().unwrap_or_default().to_string_lossy(); - if extension == "toml" { + if extension == "toml" || extension == "json" { let sb_config = load_soundboard_config(&path) .with_context(|| format!("Failed to load soundboard {}", path.display()))?; if sb_config.disabled.unwrap_or_default() { @@ -471,16 +558,39 @@ fn load_and_parse_soundboards() -> Result { } fn load_soundboard_config(soundboard_path: &Path) -> Result { - let toml_str = fs::read_to_string(&soundboard_path)?; - let soundboard_config: SoundboardConfig = toml::from_str(&toml_str)?; + let file_str = fs::read_to_string(&soundboard_path)?; + let soundboard_config: SoundboardConfig; + + if PathBuf::from(soundboard_path) + .extension() + .unwrap_or_default() + .to_string_lossy() + == "toml" + { + soundboard_config = toml::from_str(&file_str)?; + } else { + soundboard_config = serde_json::from_str(&file_str)?; + } Ok(soundboard_config) } fn check_soundboard_config_mutated_on_disk(path: &Path, last_hash: u64) -> Result { - let toml_str = fs::read_to_string(&path) + let file_str = fs::read_to_string(&path) .with_context(|| format!("Failed to read_to_string {}", path.display()))?; - let soundboard_config: SoundboardConfig = - toml::from_str(&toml_str).with_context(|| format!("Failed to parse {}", path.display()))?; + + let soundboard_config: SoundboardConfig; + if PathBuf::from(path) + .extension() + .unwrap_or_default() + .to_string_lossy() + == "toml" + { + soundboard_config = toml::from_str(&file_str) + .with_context(|| format!("Failed to parse {}", path.display()))?; + } else { + soundboard_config = serde_json::from_str(&file_str) + .with_context(|| format!("Failed to parse {}", path.display()))?; + } let old_config_hash = utils::calculate_hash(&soundboard_config); @@ -517,14 +627,35 @@ fn save_soundboard_config( )); } - let pretty_string = - toml::to_string_pretty(&config).context("failed to serialize soundboard config")?; - fs::write(&soundboard_config_path, pretty_string) + let soundboard_json_path = PathBuf::from(soundboard_config_path).with_extension("json"); + + let pretty_json_string = + serde_json::to_string_pretty(&config).context("failed to serialize soundboard config")?; + fs::write(&soundboard_json_path, pretty_json_string) .with_context(|| format!("Failed to write {}", &soundboard_config_path.display()))?; + if soundboard_config_path.exists() + && soundboard_config_path + .extension() + .unwrap_or_default() + .to_string_lossy() + == "toml" + { + fs::rename( + soundboard_config_path, + PathBuf::from(soundboard_config_path).with_extension("toml_disabled_oldformat"), + ) + .unwrap(); + } + + // let pretty_string = + // toml::to_string_pretty(&config).context("failed to serialize soundboard config")?; + // fs::write(&soundboard_config_path, pretty_string) + // .with_context(|| format!("Failed to write {}", &soundboard_config_path.display()))?; + info!( "Saved config file at {}", - soundboard_config_path.to_str().unwrap() + soundboard_json_path.to_str().unwrap() ); Ok(()) } diff --git a/src/telegram.rs b/src/telegram.rs index 06e5b0a..ddbb90f 100644 --- a/src/telegram.rs +++ b/src/telegram.rs @@ -1,6 +1,6 @@ #![allow(dead_code)] use super::download; -use super::{config, sound}; +use super::{app_config, sound, soundboards}; use anyhow::{anyhow, Context, Result}; use crossbeam_channel::{Receiver, Sender}; use fuzzy_matcher::skim::SkimMatcherV2; @@ -70,12 +70,9 @@ async fn download_file(api: &Api, file_id: &str, file_name: &str) -> Result, - sound_config: config::SoundConfig, -) -> Result<()> { +fn play_sound(sender: &Sender, sound_id: soundboards::SoundId) -> Result<()> { Ok(sender.send(sound::Message::PlaySound( - sound_config, + sound_id, sound::SoundDevices::Both, ))?) } @@ -86,75 +83,48 @@ fn send_new_sound_config( ext: String, path: String, ) -> Result<()> { - let mut new_sound_path = config::get_soundboards_path()?; - new_sound_path.push("telegram"); - let full_name = name.clone() + &ext; - new_sound_path.push(&full_name); - - info!("new path for incoming sound {}", &new_sound_path.display()); - let config = config::MainConfig::read(); - - let sound_config = config::SoundConfig { - name, - full_path: new_sound_path.to_str().unwrap().to_owned(), - path: full_name, - ..config::SoundConfig::default() - }; - - let telegram_soundboard_index = config.soundboards.iter().position(|s| s.name == "telegram"); - - let mut sound_exists = false; - if let Some(telegram_soundboard_index) = telegram_soundboard_index { - let telegram_soundboard = &config.soundboards[telegram_soundboard_index]; - let maybe_sound = telegram_soundboard - .sounds - .as_ref() - .unwrap() - .iter() - .find(|s| s.full_path == new_sound_path.to_str().unwrap()); - if maybe_sound.is_some() { - sound_exists = true; - } - } + let soundboards = soundboards::get_soundboards(); + let telegram_soundboard = soundboards.values().find(|s| s.get_name() == "telegram"); - if !sound_exists { - let mut telegram_sounds_dir = config::get_soundboards_path()?; - telegram_sounds_dir.push("telegram"); - if !telegram_sounds_dir.exists() { - std::fs::create_dir(telegram_sounds_dir)?; - } - std::fs::copy(&path, &new_sound_path).with_context(|| { - format!( - "cant copy file from {} to {}", - &path, - &new_sound_path.display() - ) - })?; - - if let Some(telegram_soundboard_index) = telegram_soundboard_index { - let mut telegram_soundboard = config.soundboards[telegram_soundboard_index].clone(); - telegram_soundboard - .sounds - .as_mut() - .unwrap() - .push(sound_config.clone()); - config::MainConfig::change_soundboard(telegram_soundboard_index, telegram_soundboard)?; + let full_name = name.clone() + &ext; + let new_source = soundboards::Source::Local { path: full_name }; + let send_sound_id; + + if let Some(telegram_soundboard) = telegram_soundboard { + let sound = telegram_soundboard + .get_sounds() + .values() + .find(|s| *s.get_source() == new_source); + if let Some(sound) = sound { + send_sound_id = *sound.get_id(); } else { - let mut sb = config::SoundboardConfig::default(); - sb.name = "telegram".to_string(); - let mut new_path = config::get_soundboards_path()?; - new_path.push("telegram.toml"); - sb.path = new_path.to_str().unwrap().to_owned(); - sb.sounds = Some(Vec::new()); - sb.sounds.as_mut().unwrap().push(sound_config.clone()); - config::MainConfig::add_soundboard(sb)?; + let mut telegram_soundboard = telegram_soundboard.clone(); + let new_sound = soundboards::Sound::new(&name, new_source)?; + send_sound_id = *new_sound.get_id(); + let pathbuf = PathBuf::from(path); + if let Err(err) = + telegram_soundboard.add_sound_with_file_path(new_sound.clone(), &pathbuf) + { + warn!("local sound file already exists {}", err); + telegram_soundboard.add_sound(new_sound)?; + } + telegram_soundboard.save_to_disk()?; + soundboards::update_soundboards(telegram_soundboard)?; + } + } else { + let mut new_soundboard = soundboards::Soundboard::new("telegram")?; + let new_sound = soundboards::Sound::new(&name, new_source)?; + send_sound_id = *new_sound.get_id(); + let pathbuf = PathBuf::from(path); + if let Err(err) = new_soundboard.add_sound_with_file_path(new_sound.clone(), &pathbuf) { + warn!("local sound file already exists {}", err); + new_soundboard.add_sound(new_sound)?; } + new_soundboard.save_to_disk()?; + soundboards::update_soundboards(new_soundboard)?; } - Ok(sender.send(sound::Message::PlaySound( - sound_config, - sound::SoundDevices::Both, - ))?) + play_sound(sender, send_sound_id) } async fn handle_audio(api: &Api, sender: &Sender, audio: &Audio) -> Result { @@ -278,13 +248,13 @@ async fn handle_sound_command( return; } - let mut possible_matches: Vec<(i64, config::SoundConfig)> = Vec::new(); + let mut possible_matches: Vec<(i64, soundboards::Sound)> = Vec::new(); let matcher = SkimMatcherV2::default(); let max_matches = 8; - for soundboard in &config::MainConfig::read().soundboards { - for sound in soundboard.sounds.as_ref().unwrap() { - if let Some(score) = matcher.fuzzy_match(&sound.name, &raw_args) { + for soundboard in soundboards::get_soundboards().values() { + for sound in soundboard.get_sounds().values() { + if let Some(score) = matcher.fuzzy_match(sound.get_name(), &raw_args) { if possible_matches.len() < max_matches { possible_matches.push((score, sound.clone())); possible_matches.sort_unstable_by_key(|e| std::cmp::Reverse(e.0)); @@ -310,7 +280,7 @@ async fn handle_sound_command( acc.push( InlineKeyboardButton::with_callback_data_struct( acc.len().to_string(), - &CallbackSoundSelectionData::new(method, elem.1.name.clone()), + &CallbackSoundSelectionData::new(method, elem.1.get_name()), ) .unwrap(), ); @@ -319,7 +289,7 @@ async fn handle_sound_command( let mut index: usize = 0; let all_matches_string = possible_matches.iter().fold(String::new(), |acc, elem| { - let res = format!("{} \n {}. {} ({})", acc, index, elem.1.name.clone(), elem.0); + let res = format!("{} \n {}. {} ({})", acc, index, elem.1.get_name(), elem.0); index += 1; res }); @@ -343,10 +313,10 @@ async fn handle_sound_command( } fn play_sound_with_name(sender: &Sender, name: &str) { - for soundboard in &config::MainConfig::read().soundboards { - for sound in soundboard.sounds.as_ref().unwrap() { - if sound.name == name { - send_sound_config(sender, sound.clone()).expect("sound channel error"); + for soundboard in soundboards::get_soundboards().values() { + for sound in soundboard.get_sounds().values() { + if sound.get_name() == name { + play_sound(sender, *sound.get_id()).expect("sound channel error"); } } } @@ -354,9 +324,9 @@ fn play_sound_with_name(sender: &Sender, name: &str) { async fn send_sound_with_name(api: &Api, message: Message, name: &str) -> Result<()> { let mut maybe_sound = None; - for soundboard in &config::MainConfig::read().soundboards { - for sound in soundboard.sounds.as_ref().unwrap() { - if sound.name == name { + for soundboard in soundboards::get_soundboards().values() { + for sound in soundboard.get_sounds().values() { + if sound.get_name() == name { maybe_sound = Some(sound.clone()); } } @@ -364,18 +334,21 @@ async fn send_sound_with_name(api: &Api, message: Message, name: &str) -> Result if let Some(sound) = maybe_sound { let sound_clone = sound.clone(); - let local_path = download::get_local_path_from_sound_config_async(&sound_clone).await?; + let local_path = + task::spawn_blocking(move || download::get_local_path_from_sound_config(&sound_clone)) + .await??; let file = tgbot::types::InputFile::path(local_path.as_path()) .await .unwrap(); - let method = tgbot::methods::SendAudio::new(message.get_chat_id(), file).title(&sound.name); + let method = + tgbot::methods::SendAudio::new(message.get_chat_id(), file).title(sound.get_name()); if let Err(err) = api.execute(method).await { error!("telegram api error: {}", err); let file = tgbot::types::InputFile::path(local_path.as_path()) .await .unwrap(); - let method = - tgbot::methods::SendDocument::new(message.get_chat_id(), file).caption(&sound.name); + let method = tgbot::methods::SendDocument::new(message.get_chat_id(), file) + .caption(sound.get_name()); if let Err(err) = api.execute(method).await { error!("telegram api error: {}", err); }