/* mood --- A music player governed by your moods. Copyright (C) 2026 tslil clingman This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ use std::{process::Command, process::exit}; mod albums; mod cli; mod config; mod intuition; mod state; mod trajectory; use crate::cli::{Action, parse}; use crate::config::Config; use crate::state::State; fn mpc_load_and_play(path: &str) { _ = Command::new("mpc").arg("clear").output(); _ = Command::new("mpc").args(["add", path]).output(); _ = Command::new("mpc").arg("play").output(); } fn notify(text: &str) { if let Err(e) = Command::new("notify-send") .args(["-t", "5000", "-a", "mood", text]) .status() { eprintln!("Failed to show notification: {e}"); } } fn load_state_no_default() -> State { State::try_load().unwrap_or_else(|e| { eprintln!("Failed to load state: {e}, nothing to dump."); exit(1); }) } fn main() { let action = parse(); if action.is_none() { return; } let action = action.unwrap(); match action { Action::DumpIntuition => { let state = load_state_no_default(); println!( "{}", state.dump_intuition().unwrap_or_else(|e| { eprintln!("Failed to serialise state (dump-intuition): {e}"); exit(1); }) ); } Action::DumpTrajectory => { let state = load_state_no_default(); println!( "{}", state.dump_trajectory().unwrap_or_else(|e| { eprintln!("Failed to serialise state (dump-trajectory): {e}"); exit(1); }) ); } Action::NewState => { let default = State::default(); if let Err(e) = default.try_save() { eprintln!("Failed to write new state: {e}"); exit(1); } println!("New state written."); } Action::NewConfig => { let default = Config::default(); if let Err(e) = default.try_save() { eprintln!("Failed to write new config: {e}"); exit(1); } println!("New config written."); } Action::Run => { let config = match Config::try_load() { Ok(c) => c, Err(e) => { eprintln!("Failed to load config: {e}, writing defaults."); let default = Config::default(); if let Err(se) = default.try_save() { panic!("Failed to save default config: {se}"); } default } }; let mut state = match State::try_load() { Ok(s) => s, Err(e) => { eprintln!("Failed to load state: {e}"); State::default() } }; if let Some((next_album, prob)) = state.next(&config) { let text = format!("Now playing: {next_album} ({:.2}%)", prob * 100.0); println!("{text}"); mpc_load_and_play(&next_album); notify(&text); } else { println!("No next album available."); } if let Err(e) = state.try_save() { panic!("Failed to save state: {e}"); } } } }