now use single settings file instead of many; minor refactoring

This commit is contained in:
2025-07-06 22:26:33 +03:00
parent 3ba7a79010
commit 26eec0c0ef
5 changed files with 156 additions and 127 deletions
Generated
+8 -6
View File
@@ -2829,6 +2829,8 @@ dependencies = [
"metadata", "metadata",
"rfd", "rfd",
"rodio", "rodio",
"serde",
"serde_json",
] ]
[[package]] [[package]]
@@ -3101,18 +3103,18 @@ dependencies = [
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.217" version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.217" version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -3121,9 +3123,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.138" version = "1.0.140"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
dependencies = [ dependencies = [
"itoa", "itoa",
"memchr", "memchr",
+3 -1
View File
@@ -17,8 +17,10 @@ eframe = "0.31.1"
egui_material_icons = "0.3.0" egui_material_icons = "0.3.0"
rfd = "0.15.3" rfd = "0.15.3"
dirs = "6.0.0" 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" metadata = "0.1.10"
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140"
[profile.release] [profile.release]
strip = true strip = true
+76 -93
View File
@@ -1,3 +1,6 @@
mod pw;
pub mod settings;
use eframe::{CreationContext, Frame, NativeOptions}; use eframe::{CreationContext, Frame, NativeOptions};
use egui::{ use egui::{
Button, CentralPanel, ComboBox, Context, Label, ScrollArea, Separator, Slider, TextEdit, Ui, 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 metadata::media_file::MediaFileMetadata;
use rfd::FileDialog; use rfd::FileDialog;
use rodio::{Decoder, OutputStream, OutputStreamHandle, Sink}; use rodio::{Decoder, OutputStream, OutputStreamHandle, Sink};
use std::io::Write; pub use settings::Settings;
use std::{fs, path::PathBuf}; use std::{fs, path::PathBuf};
mod pw; #[derive(PartialEq)]
enum PlayerState { enum PlayerState {
PLAYING, PLAYING,
PAUSED, PAUSED,
@@ -24,9 +26,12 @@ impl Default for PlayerState {
} }
pub struct App { pub struct App {
saved_settings: Settings,
player_position: f32, player_position: f32,
prev_player_position: f32, prev_player_position: f32,
max_player_position: f32, max_player_position: f32,
volume: f32, volume: f32,
player_state: PlayerState, player_state: PlayerState,
@@ -47,46 +52,88 @@ pub struct App {
audio_sink: Sink, audio_sink: Sink,
} }
impl App { impl eframe::App for App {
pub fn new(_cc: &CreationContext<'_>) -> Self { fn update(&mut self, ctx: &Context, _frame: &mut Frame) {
let saved_dirs_path = dirs::config_dir().unwrap().join("pwsp").join("saved_dirs"); CentralPanel::default().show(ctx, |ui| {
let saved_dirs_content = fs::read_to_string(&saved_dirs_path).unwrap_or_default(); // Render UI
let directories: Vec<_> = saved_dirs_content.lines().map(PathBuf::from).collect(); self.render_ui(ui);
let current_directory = match directories.is_empty() {
false => Some(0), // Load all available input devices
true => None, 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 {
let saved_mic_path = dirs::config_dir().unwrap().join("pwsp").join("saved_mic"); current_settings.save_to_file(
let selected_input_device = fs::read_to_string(&saved_mic_path).unwrap_or_default(); &dirs::config_dir()
.unwrap_or_default()
let saved_volume_path = dirs::config_dir()
.unwrap()
.join("pwsp") .join("pwsp")
.join("saved_volume"); .join("pwsp.json"),
let saved_volume = fs::read_to_string(&saved_volume_path).unwrap_or_default(); );
let volume = match saved_volume.is_empty() { self.saved_settings = current_settings;
true => 1.0, }
false => saved_volume.parse().unwrap(),
// 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<'_>, settings: Settings) -> Self {
let (_audio_stream, _audio_stream_handle) = OutputStream::try_default().unwrap(); let (_audio_stream, _audio_stream_handle) = OutputStream::try_default().unwrap();
let audio_sink = Sink::try_new(&_audio_stream_handle).unwrap(); let audio_sink = Sink::try_new(&_audio_stream_handle).unwrap();
audio_sink.pause(); audio_sink.pause();
Self { Self {
saved_settings: settings.clone(),
player_position: 0.0, player_position: 0.0,
prev_player_position: 0.0, prev_player_position: 0.0,
max_player_position: 1.0, max_player_position: 1.0,
volume, volume: settings.saved_volume,
player_state: PlayerState::PAUSED, player_state: PlayerState::PAUSED,
directories, directories: settings.saved_dirs.clone(),
directory_to_delete: None, 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(), available_input_devices: Vec::new(),
current_file: PathBuf::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) { fn render_ui(&mut self, ui: &mut Ui) {
self.render_player(ui); self.render_player(ui);
@@ -186,7 +189,7 @@ impl App {
Slider::new(&mut self.player_position, 0.0..=self.max_player_position) Slider::new(&mut self.player_position, 0.0..=self.max_player_position)
.show_value(false); .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); ui.add_space(10.0);
@@ -278,13 +281,9 @@ impl App {
fn handle_directory_adding(&mut self) { fn handle_directory_adding(&mut self) {
if let Some(dirs) = FileDialog::pick_folders(Default::default()) { 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 { for path in dirs {
if !self.directories.contains(&path) { if !self.directories.contains(&path) {
self.directories.push(path.clone()); self.directories.push(path.clone());
writeln!(file, "{}", path.display()).ok();
}
} }
} }
} }
@@ -302,16 +301,6 @@ impl App {
self.directories.remove(index); self.directories.remove(index);
self.directory_to_delete = None; 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::<Vec<_>>()
.join("\n");
fs::write(saved_dirs_path, content).ok();
} }
} }
@@ -450,13 +439,7 @@ impl App {
} }
} }
impl eframe::App for App { pub fn run(settings: Settings) -> Result<(), eframe::Error> {
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> {
let options = NativeOptions { let options = NativeOptions {
vsync: true, vsync: true,
centered: true, centered: true,
@@ -475,7 +458,7 @@ pub fn run() -> Result<(), eframe::Error> {
options, options,
Box::new(|cc| { Box::new(|cc| {
egui_material_icons::initialize(&cc.egui_ctx); egui_material_icons::initialize(&cc.egui_ctx);
Ok(Box::new(App::new(cc))) Ok(Box::new(App::new(cc, settings)))
}), }),
) )
} }
+50
View File
@@ -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<PathBuf>,
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<u8> = 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
}
+13 -21
View File
@@ -3,30 +3,22 @@ use std::fs;
mod app; mod app;
fn main() -> Result<(), eframe::Error> { fn main() -> Result<(), eframe::Error> {
create_dirs(); let settings = generate_settings();
app::run() app::run(settings)
} }
fn create_dirs() { fn generate_settings() -> app::Settings {
let config_dir_path = dirs::config_dir().unwrap().join("pwsp"); 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(); fs::create_dir_all(&config_dir_path).ok();
if !fs::exists(config_dir_path.join("saved_dirs")) let settings: app::Settings;
.ok() if config_path.exists() {
.unwrap_or(false) settings = app::settings::load_from_file(&config_path);
{ } else {
fs::File::create(config_dir_path.join("saved_dirs")).ok(); settings = app::Settings::default();
} settings.save_to_file(&config_path);
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();
} }
settings
} }