From 3f289981bd9c8e800a6cd310d4b1f2f36634c9f1 Mon Sep 17 00:00:00 2001 From: tslil Date: Mon, 13 Jul 2026 15:06:41 +0100 Subject: Pure history --- src/state.rs | 81 ++++++++++++++++++++++++++++++++++++------------------------ 1 file changed, 49 insertions(+), 32 deletions(-) (limited to 'src/state.rs') diff --git a/src/state.rs b/src/state.rs index b9a6618..a04412f 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,36 +1,36 @@ -use std::{collections::HashSet, fs}; - -use derive_more::Display; +use std::{collections::HashSet, fmt, fs}; +use directories::ProjectDirs; use expanduser::expanduser; use serde::{Deserialize, Serialize}; use walkdir::WalkDir; -use crate::{ - config::{Config, default_state_path}, - learner::Learner, - trajectory::Trajectory, -}; +use crate::{config::Config, learner::Learner, trajectory::Trajectory}; + +fn default_state_path() -> String { + let name = "mood.bin"; + ProjectDirs::from("qualifier", "organisation", "mood") + .and_then(|pd| pd.config_dir().join(name).to_str().map(|x| x.to_string())) + .unwrap_or(format!("~/.config/mood/{name}")) +} #[derive(Serialize, Deserialize, Default)] pub struct State { - pub config: Config, trajectory: Trajectory, learner: Learner, albums: HashSet, } impl State { - pub fn next(&mut self) -> Option<(String, f32)> { - let learning = self.trajectory.step(self.config.skip_window_secs); + pub fn next(&mut self, config: &Config) -> Option<(String, f32)> { + let learning = self.trajectory.step(config.skip_window_secs); if let Some(ref learning) = learning { - println!("Obtained learning: {learning:?}"); - self.learner.learn(&self.config, learning); + self.learner.learn(learning); } if let Some((new_album, prob)) = self.learner.sample( self.trajectory.slice(), &learning.as_ref().into(), &self.albums, - self.config.temperature, + config, ) { self.trajectory.log(&new_album); Some((new_album, prob)) @@ -40,36 +40,39 @@ impl State { } } -#[derive(Display)] -#[display("+{added}/-{removed}, total {new_total}")] pub struct UpdateResult { added: usize, removed: usize, new_total: usize, } +impl fmt::Display for UpdateResult { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "+{}/-{}, total {}", self.added, self.removed, self.new_total) + } +} + impl State { - pub fn load(path: &str) -> Result { - expanduser(path) + pub fn try_load() -> Result { + expanduser(default_state_path()) .map_err(|e| e.to_string()) - .and_then(|p| fs::read_to_string(p).map_err(|e| e.to_string())) - .and_then(|contents| ron::from_str(&contents).map_err(|e| e.to_string())) + .and_then(|p| fs::read(p).map_err(|e| e.to_string())) + .and_then(|contents| bincode::deserialize(&contents).map_err(|e| e.to_string())) } pub fn try_save(&self) -> Result<(), String> { let str_path = default_state_path(); let path = expanduser(&str_path).map_err(|e| e.to_string())?; - _ = path.parent().map(|parent| std::fs::create_dir_all(parent)); - let serialised = ron::ser::to_string_pretty(self, ron::ser::PrettyConfig::default()) - .map_err(|e| format!("Failed to serialize state: {}", e))?; + _ = path.parent().map(std::fs::create_dir_all); + let serialised = + bincode::serialize(self).map_err(|e| format!("Failed to serialize state: {}", e))?; fs::write(&path, serialised) .map_err(|e| format!("Failed to write state file at {}: {}", str_path, e))?; Ok(()) } - pub fn update_albums(&mut self) -> UpdateResult { - let roots: Vec<_> = self - .config + pub fn update_albums(&mut self, config: &Config) -> UpdateResult { + let roots: Vec<_> = config .music_roots .iter() .filter_map(|x| expanduser(x).ok().filter(|p| p.is_dir())) @@ -77,14 +80,14 @@ impl State { let mut new_albums: HashSet = HashSet::new(); - for root_path in roots { + for root_path in &roots { for entry in WalkDir::new(&root_path) { let entry = match entry { Ok(e) if e.path().is_dir() => e, _ => continue, }; - if !self.contains_audio(&entry) { + if !Self::contains_audio(&entry, config) { continue; } @@ -113,21 +116,35 @@ impl State { } } - fn contains_audio(&self, entry: &walkdir::DirEntry) -> bool { + fn contains_audio(entry: &walkdir::DirEntry, config: &Config) -> bool { fs::read_dir(entry.path()).ok().is_some_and(|mut entries| { entries.any(|e| { e.as_ref() - .is_ok_and(|entry| self.is_audio_file(&entry.path())) + .is_ok_and(|entry| Self::is_audio_file(&entry.path(), config)) }) }) } - fn is_audio_file(&self, path: &std::path::Path) -> bool { + fn is_audio_file(path: &std::path::Path, config: &Config) -> bool { !path.is_dir() && path .extension() .and_then(|ext| ext.to_str()) .map(|s| s.to_lowercase()) - .is_some_and(|ext| self.config.audio_exts.contains(&format!(".{ext}"))) + .is_some_and(|ext| config.audio_exts.contains(&format!(".{ext}"))) + } +} + +impl State { + pub fn dump_albums(&self) -> Result { + serde_json::to_string_pretty(&self.albums).map_err(|e| e.to_string()) + } + + pub fn dump_learner(&self) -> Result { + serde_json::to_string_pretty(&self.learner).map_err(|e| e.to_string()) + } + + pub fn dump_trajectory(&self) -> Result { + serde_json::to_string_pretty(&self.trajectory).map_err(|e| e.to_string()) } } -- cgit v1.2.3