From 26eec0c0efadf74d41a610aa01dcc1b4541714be Mon Sep 17 00:00:00 2001 From: arabian Date: Sun, 6 Jul 2025 22:26:33 +0300 Subject: [PATCH] now use single settings file instead of many; minor refactoring --- Cargo.lock | 14 ++-- Cargo.toml | 4 +- src/app.rs | 181 ++++++++++++++++++++------------------------ src/app/settings.rs | 50 ++++++++++++ src/main.rs | 34 ++++----- 5 files changed, 156 insertions(+), 127 deletions(-) create mode 100644 src/app/settings.rs diff --git a/Cargo.lock b/Cargo.lock index 81d2632..f806247 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2829,6 +2829,8 @@ dependencies = [ "metadata", "rfd", "rodio", + "serde", + "serde_json", ] [[package]] @@ -3101,18 +3103,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.217" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.217" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", @@ -3121,9 +3123,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.138" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "itoa", "memchr", diff --git a/Cargo.toml b/Cargo.toml index 8f413f9..de952ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,8 +17,10 @@ eframe = "0.31.1" egui_material_icons = "0.3.0" rfd = "0.15.3" dirs = "6.0.0" -rodio = {version = "0.20.1", default-features = false, features = ["symphonia-all"]} +rodio = { version = "0.20.1", default-features = false, features = ["symphonia-all"] } metadata = "0.1.10" +serde = { version = "1.0.219", features = ["derive"] } +serde_json = "1.0.140" [profile.release] strip = true diff --git a/src/app.rs b/src/app.rs index 4cb9ccc..f79ea69 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,3 +1,6 @@ +mod pw; +pub mod settings; + use eframe::{CreationContext, Frame, NativeOptions}; use egui::{ Button, CentralPanel, ComboBox, Context, Label, ScrollArea, Separator, Slider, TextEdit, Ui, @@ -7,11 +10,10 @@ use egui_material_icons::icons; use metadata::media_file::MediaFileMetadata; use rfd::FileDialog; use rodio::{Decoder, OutputStream, OutputStreamHandle, Sink}; -use std::io::Write; +pub use settings::Settings; use std::{fs, path::PathBuf}; -mod pw; - +#[derive(PartialEq)] enum PlayerState { PLAYING, PAUSED, @@ -24,9 +26,12 @@ impl Default for PlayerState { } pub struct App { + saved_settings: Settings, + player_position: f32, prev_player_position: f32, max_player_position: f32, + volume: f32, player_state: PlayerState, @@ -47,46 +52,88 @@ pub struct App { audio_sink: Sink, } +impl eframe::App for App { + fn update(&mut self, ctx: &Context, _frame: &mut Frame) { + CentralPanel::default().show(ctx, |ui| { + // Render UI + self.render_ui(ui); + + // Load all available input devices + self.available_input_devices = pw::get_input_devices().unwrap_or_default(); + + // Save new settings + let current_settings = Settings { + saved_dirs: self.directories.clone(), + saved_mic: self.selected_input_device.clone(), + saved_volume: self.volume, + }; + if current_settings != self.saved_settings { + current_settings.save_to_file( + &dirs::config_dir() + .unwrap_or_default() + .join("pwsp") + .join("pwsp.json"), + ); + self.saved_settings = current_settings; + } + + // Pause audio_sink on audio end + if self.audio_sink.len() == 0 && !self.audio_sink.is_paused() { + self.audio_sink.pause(); + } + + // Change player_state based on audio_sink state + self.player_state = match self.audio_sink.is_paused() { + true => PlayerState::PAUSED, + false => PlayerState::PLAYING, + }; + + // Handle changing player position + if self.player_position != self.prev_player_position { + let mut target_pos = self.player_position - 0.5; + target_pos = target_pos.clamp(0f32, self.max_player_position); + + let target_pos_dur = core::time::Duration::from_secs_f32(target_pos); + self.audio_sink.try_seek(target_pos_dur).unwrap(); + self.prev_player_position = self.player_position; + } + + // Update UI when playing + if self.player_state == PlayerState::PLAYING { + ctx.request_repaint(); + self.audio_sink.set_volume(self.volume); + self.player_position = self.audio_sink.get_pos().as_secs_f32(); + } + + self.prev_player_position = self.player_position; + }); + } +} + impl App { - pub fn new(_cc: &CreationContext<'_>) -> Self { - let saved_dirs_path = dirs::config_dir().unwrap().join("pwsp").join("saved_dirs"); - let saved_dirs_content = fs::read_to_string(&saved_dirs_path).unwrap_or_default(); - let directories: Vec<_> = saved_dirs_content.lines().map(PathBuf::from).collect(); - let current_directory = match directories.is_empty() { - false => Some(0), - true => None, - }; - - let saved_mic_path = dirs::config_dir().unwrap().join("pwsp").join("saved_mic"); - let selected_input_device = fs::read_to_string(&saved_mic_path).unwrap_or_default(); - - let saved_volume_path = dirs::config_dir() - .unwrap() - .join("pwsp") - .join("saved_volume"); - let saved_volume = fs::read_to_string(&saved_volume_path).unwrap_or_default(); - let volume = match saved_volume.is_empty() { - true => 1.0, - false => saved_volume.parse().unwrap(), - }; - + pub fn new(_cc: &CreationContext<'_>, settings: Settings) -> Self { let (_audio_stream, _audio_stream_handle) = OutputStream::try_default().unwrap(); let audio_sink = Sink::try_new(&_audio_stream_handle).unwrap(); audio_sink.pause(); Self { + saved_settings: settings.clone(), + player_position: 0.0, prev_player_position: 0.0, max_player_position: 1.0, - volume, + volume: settings.saved_volume, player_state: PlayerState::PAUSED, - directories, + directories: settings.saved_dirs.clone(), directory_to_delete: None, - current_directory, + current_directory: match settings.saved_dirs.len() { + 0 => None, + _ => Some(0), + }, - selected_input_device, + selected_input_device: settings.saved_mic, available_input_devices: Vec::new(), current_file: PathBuf::new(), @@ -99,50 +146,6 @@ impl App { } } - fn upd(&mut self, ui: &mut Ui, ctx: &Context, _frame: &mut Frame) { - self.render_ui(ui); - - self.available_input_devices = pw::get_input_devices().unwrap(); - - let saved_mic_path = dirs::config_dir().unwrap().join("pwsp").join("saved_mic"); - let saved_mic_content = fs::read_to_string(&saved_mic_path).unwrap_or_default(); - if self.selected_input_device != saved_mic_content { - fs::write(saved_mic_path, self.selected_input_device.clone()).ok(); - } - let saved_volume_path = dirs::config_dir() - .unwrap() - .join("pwsp") - .join("saved_volume"); - fs::write(saved_volume_path, self.volume.to_string()).ok(); - - if self.audio_sink.len() == 0 && !self.audio_sink.is_paused() { - self.audio_sink.pause(); - } - - self.player_state = match self.audio_sink.is_paused() { - true => PlayerState::PAUSED, - false => PlayerState::PLAYING, - }; - - if self.player_position != self.prev_player_position { - let mut target_pos = self.player_position - 0.5; - target_pos = target_pos.clamp(0f32, self.max_player_position); - - let target_pos_dur = core::time::Duration::from_secs_f32(target_pos); - self.audio_sink.try_seek(target_pos_dur).unwrap(); - self.prev_player_position = self.player_position; - } - - if let PlayerState::PLAYING = self.player_state { - ctx.request_repaint(); - self.audio_sink.set_volume(self.volume); - - self.player_position = self.audio_sink.get_pos().as_secs_f32(); - } - - self.prev_player_position = self.player_position; - } - fn render_ui(&mut self, ui: &mut Ui) { self.render_player(ui); @@ -186,7 +189,7 @@ impl App { Slider::new(&mut self.player_position, 0.0..=self.max_player_position) .show_value(false); - let volume_slider = Slider::new(&mut self.volume, 0.0..=1.0).show_value(false); + let volume_slider = Slider::new(&mut self.volume, 0.0..=5.0).show_value(false); ui.add_space(10.0); @@ -278,13 +281,9 @@ impl App { fn handle_directory_adding(&mut self) { if let Some(dirs) = FileDialog::pick_folders(Default::default()) { - let saved_dirs_path = dirs::config_dir().unwrap().join("pwsp").join("saved_dirs"); - if let Ok(mut file) = fs::OpenOptions::new().append(true).open(saved_dirs_path) { - for path in dirs { - if !self.directories.contains(&path) { - self.directories.push(path.clone()); - writeln!(file, "{}", path.display()).ok(); - } + for path in dirs { + if !self.directories.contains(&path) { + self.directories.push(path.clone()); } } } @@ -302,16 +301,6 @@ impl App { self.directories.remove(index); self.directory_to_delete = None; - - let saved_dirs_path = dirs::config_dir().unwrap().join("pwsp").join("saved_dirs"); - let content = self - .directories - .iter() - .map(|p| p.display().to_string()) - .collect::>() - .join("\n"); - - fs::write(saved_dirs_path, content).ok(); } } @@ -450,13 +439,7 @@ impl App { } } -impl eframe::App for App { - fn update(&mut self, ctx: &Context, frame: &mut Frame) { - CentralPanel::default().show(ctx, |ui| self.upd(ui, ctx, frame)); - } -} - -pub fn run() -> Result<(), eframe::Error> { +pub fn run(settings: Settings) -> Result<(), eframe::Error> { let options = NativeOptions { vsync: true, centered: true, @@ -475,7 +458,7 @@ pub fn run() -> Result<(), eframe::Error> { options, Box::new(|cc| { egui_material_icons::initialize(&cc.egui_ctx); - Ok(Box::new(App::new(cc))) + Ok(Box::new(App::new(cc, settings))) }), ) } diff --git a/src/app/settings.rs b/src/app/settings.rs new file mode 100644 index 0000000..4f26f9d --- /dev/null +++ b/src/app/settings.rs @@ -0,0 +1,50 @@ +use serde::{Deserialize, Serialize}; +use std::fs; +use std::io::{Read, Write}; +use std::path::PathBuf; + +#[derive(Serialize, Deserialize, Debug, PartialEq)] +pub struct Settings { + pub saved_dirs: Vec, + pub saved_mic: String, + pub saved_volume: f32, +} + +impl Default for Settings { + fn default() -> Self { + Settings { + saved_dirs: Vec::new(), + saved_mic: String::new(), + saved_volume: 1.0, + } + } +} + +impl Clone for Settings { + fn clone(&self) -> Self { + Settings { + saved_dirs: self.saved_dirs.clone(), + saved_mic: self.saved_mic.clone(), + saved_volume: self.saved_volume, + } + } +} + +impl Settings { + pub fn save_to_file(&self, file_path: &PathBuf) { + let mut file = fs::File::create(file_path).unwrap(); + let buf = serde_json::to_vec(&self).unwrap(); + file.write_all(&buf[..]).ok(); + } +} + +pub fn load_from_file(file_path: &PathBuf) -> Settings { + let mut file = fs::File::open(file_path).unwrap(); + let mut buf: Vec = vec![]; + file.read_to_end(&mut buf).ok(); + + let mut settings: Settings = serde_json::from_slice(&buf[..]).unwrap(); + settings.saved_volume = settings.saved_volume.clamp(0.0, 1.0); + + settings +} diff --git a/src/main.rs b/src/main.rs index 3f6b4cc..a01ce7c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,30 +3,22 @@ use std::fs; mod app; fn main() -> Result<(), eframe::Error> { - create_dirs(); - app::run() + let settings = generate_settings(); + app::run(settings) } -fn create_dirs() { - let config_dir_path = dirs::config_dir().unwrap().join("pwsp"); +fn generate_settings() -> app::Settings { + let config_dir_path = dirs::config_dir().unwrap_or_default().join("pwsp"); + let config_path = config_dir_path.join("pwsp.json"); fs::create_dir_all(&config_dir_path).ok(); - if !fs::exists(config_dir_path.join("saved_dirs")) - .ok() - .unwrap_or(false) - { - fs::File::create(config_dir_path.join("saved_dirs")).ok(); - } - if !fs::exists(config_dir_path.join("saved_mic")) - .ok() - .unwrap_or(false) - { - fs::File::create(config_dir_path.join("saved_mic")).ok(); - } - if !fs::exists(config_dir_path.join("saved_volume")) - .ok() - .unwrap_or(false) - { - fs::File::create(config_dir_path.join("saved_volume")).ok(); + let settings: app::Settings; + if config_path.exists() { + settings = app::settings::load_from_file(&config_path); + } else { + settings = app::Settings::default(); + settings.save_to_file(&config_path); } + + settings }