diff options
| author | tslil <tslil@posteo.de> | 2026-07-14 12:20:44 +0100 |
|---|---|---|
| committer | tslil <tslil@posteo.de> | 2026-07-14 13:02:51 +0100 |
| commit | 156cbfe2b0383521f540109682d98d35c88d65f8 (patch) | |
| tree | e4607f60c1c58ad566d2d86488a601d58eee4220 /src/state.rs | |
| parent | 861fb134ad6d77faeb23e9571a31e0c6031d7374 (diff) | |
don't cache album list, use compression only on json for ease of inspection
Diffstat (limited to 'src/state.rs')
| -rw-r--r-- | src/state.rs | 119 |
1 files changed, 26 insertions, 93 deletions
diff --git a/src/state.rs b/src/state.rs index 425e5d7..195d5cf 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,16 +1,15 @@ -use std::{collections::HashSet, fmt, fs}; +use std::fs; + use directories::ProjectDirs; use expanduser::expanduser; use serde::{Deserialize, Serialize}; -use walkdir::WalkDir; -use zstd; -use crate::{config::Config, learner::Learner, trajectory::Trajectory}; +use crate::{albums, config::Config, learner::Learner, trajectory::Trajectory}; fn default_state_path() -> String { - let name = "mood.bin.zstd"; + let name = "mood.json.zstd"; ProjectDirs::from("qualifier", "organisation", "mood") - .and_then(|pd| pd.config_dir().join(name).to_str().map(|x| x.to_string())) + .and_then(|pd| pd.config_dir().join(name).to_str().map(std::string::ToString::to_string)) .unwrap_or(format!("~/.config/mood/{name}")) } @@ -18,11 +17,19 @@ fn default_state_path() -> String { pub struct State { trajectory: Trajectory, learner: Learner, - albums: HashSet<String>, } impl State { pub fn next(&mut self, config: &Config) -> Option<(String, f32)> { + let albums = albums::load(config); + if albums.is_empty() { + println!("Found no albums in {}", config.music_root); + return None; + } else { + let l = albums.len(); + println!("Found {} album{}", l, if l > 1 { "s" } else { "" }); + } + let learning = self.trajectory.step(config.skip_window_secs); if let Some(ref learning) = learning { self.learner.learn(learning); @@ -30,7 +37,7 @@ impl State { if let Some((new_album, prob)) = self.learner.sample( self.trajectory.slice(), &learning.as_ref().into(), - &self.albums, + &albums, config, ) { self.trajectory.log(&new_album); @@ -41,109 +48,35 @@ impl State { } } -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 try_load() -> Result<State, String> { expanduser(default_state_path()) .map_err(|e| e.to_string()) .and_then(|p| fs::read(p).map_err(|e| e.to_string())) .and_then(|contents| zstd::decode_all(&contents[..]).map_err(|e| e.to_string())) - .and_then(|decompressed| bincode::deserialize(&decompressed).map_err(|e| e.to_string())) + .and_then(|decompressed| { + std::str::from_utf8(&decompressed) + .map(std::string::ToString::to_string) + .map_err(|e| e.to_string()) + }) + .and_then(|utf8| serde_json::from_str(&utf8).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(std::fs::create_dir_all); - let serialised = - bincode::serialize(self).map_err(|e| format!("Failed to serialize state: {}", e))?; - let compressed = zstd::encode_all(serialised.as_slice(), 3) - .map_err(|e| format!("Failed to compress state: {}", e))?; + let serialised = serde_json::to_string_pretty(self) + .map_err(|e| format!("Failed to serialize state: {e}"))?; + let compressed = zstd::encode_all(serialised.into_bytes().as_slice(), 3) + .map_err(|e| format!("Failed to compress state: {e}"))?; fs::write(&path, compressed) - .map_err(|e| format!("Failed to write state file at {}: {}", str_path, e))?; + .map_err(|e| format!("Failed to write state file at {str_path}: {e}"))?; Ok(()) } - - 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())) - .collect(); - - let mut new_albums: HashSet<String> = HashSet::new(); - - 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, config) { - continue; - } - - if let Some(rel_str) = entry - .path() - .strip_prefix(&root_path) - .ok() - .and_then(|p| p.to_str()) - { - new_albums.insert(rel_str.to_string()); - } - } - } - - let added = new_albums.difference(&self.albums).count(); - let removed = self.albums.difference(&new_albums).count(); - self.albums.clone_from(&new_albums); - - self.trajectory.prune(&self.albums); - self.learner.prune(&self.albums); - - UpdateResult { - added, - removed, - new_total: self.albums.len(), - } - } - - 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(), config)) - }) - }) - } - - 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| config.audio_exts.contains(&format!(".{ext}"))) - } } impl State { - pub fn dump_albums(&self) -> Result<String, String> { - serde_json::to_string_pretty(&self.albums).map_err(|e| e.to_string()) - } - pub fn dump_learner(&self) -> Result<String, String> { serde_json::to_string_pretty(&self.learner).map_err(|e| e.to_string()) } |
