From a880ffd1f6be55c21c4b56d875d7dc45b9189b20 Mon Sep 17 00:00:00 2001 From: Henrik Friedrichsen Date: Sun, 21 Feb 2021 21:24:46 +0100 Subject: [PATCH] Persist volume and shuffle/repeat state --- README.md | 17 ---------------- src/config.rs | 54 +++++++++++++++++++++++++++++++++++++++++--------- src/queue.rs | 23 ++++++++------------- src/spotify.rs | 38 ++++------------------------------- 4 files changed, 57 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index a712282..86f3437 100644 --- a/README.md +++ b/README.md @@ -203,23 +203,6 @@ See the help screen by pressing `?` for a list of possible commands. ncspot will respect system proxy settings defined via the `http_proxy` environment variable. -### Initial state - -The initial state can be specified in the configuration. -It allows for example enabling shuffle per default. -Following entries can be added to the configuration file: - -``` -[saved_state] -volume = 80 -repeat = "track" -shuffle = true -``` - -- `volume` needs to be an integer value between 0 and 100 -- `repeat` can be `"track"`, `"playlist"` or any other value which defaults to no -- `shuffle` must be `"true"` or `"false"` - ### Theming [Theme generator](https://ncspot-theme-generator.vaa.red/) by [@vaarad](https://github.com/vaared). diff --git a/src/config.rs b/src/config.rs index 6ff5776..2863d2d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,11 +1,13 @@ use std::collections::HashMap; use std::path::{Path, PathBuf}; -use std::sync::{RwLock, RwLockReadGuard}; +use std::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use std::{fs, process}; use cursive::theme::Theme; use platform_dirs::AppDirs; +use crate::queue; + pub const CLIENT_ID: &str = "d420a117a32841c2b3474932e49fb54b"; #[derive(Clone, Serialize, Deserialize, Debug, Default)] @@ -14,7 +16,6 @@ pub struct ConfigValues { pub keybindings: Option>, pub theme: Option, pub use_nerdfont: Option, - pub saved_state: Option, pub audio_cache: Option, pub backend: Option, pub backend_device: Option, @@ -26,13 +27,6 @@ pub struct ConfigValues { pub gapless: Option, } -#[derive(Serialize, Deserialize, Debug, Default, Clone)] -pub struct SavedState { - pub volume: Option, - pub shuffle: Option, - pub repeat: Option, -} - #[derive(Serialize, Deserialize, Debug, Default, Clone)] pub struct ConfigTheme { pub background: Option, @@ -55,12 +49,30 @@ pub struct ConfigTheme { pub search_match: Option, } +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct UserState { + pub volume: u16, + pub shuffle: bool, + pub repeat: queue::RepeatSetting, +} + +impl Default for UserState { + fn default() -> Self { + UserState { + volume: u16::max_value(), + shuffle: false, + repeat: queue::RepeatSetting::None, + } + } +} + lazy_static! { pub static ref BASE_PATH: RwLock> = RwLock::new(None); } pub struct Config { values: RwLock, + state: RwLock, } impl Config { @@ -70,8 +82,15 @@ impl Config { process::exit(1); }); + let userstate = { + let path = config_path("userstate.toml"); + load_or_generate_default(path, |_| Ok(UserState::default()), false) + .expect("could not load user state") + }; + Self { values: RwLock::new(values), + state: RwLock::new(userstate), } } @@ -79,6 +98,23 @@ impl Config { self.values.read().expect("can't readlock config values") } + pub fn state(&self) -> RwLockReadGuard { + self.state.read().expect("can't readlock user state") + } + + pub fn with_state_mut(&self, cb: F) + where + F: Fn(RwLockWriteGuard), + { + let state_guard = self.state.write().expect("can't writelock user state"); + cb(state_guard); + + let path = config_path("userstate.toml"); + if let Err(e) = write_content_helper(path, self.state().clone()) { + error!("Could not save user state: {}", e); + } + } + pub fn build_theme(&self) -> Theme { let theme = &self.values().theme; crate::theme::load(theme) diff --git a/src/queue.rs b/src/queue.rs index 81c2634..1845c8e 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -22,24 +22,19 @@ pub struct Queue { pub queue: Arc>>, random_order: RwLock>>, current_track: RwLock>, - repeat: RwLock, spotify: Arc, cfg: Arc, } impl Queue { pub fn new(spotify: Arc, cfg: Arc) -> Queue { - let q = Queue { + Queue { queue: Arc::new(RwLock::new(Vec::new())), spotify, current_track: RwLock::new(None), - repeat: RwLock::new(RepeatSetting::None), random_order: RwLock::new(None), cfg, - }; - q.set_repeat(q.spotify.repeat); - q.set_shuffle(q.spotify.shuffle); - q + } } pub fn next_index(&self) -> Option { @@ -285,7 +280,7 @@ impl Queue { pub fn next(&self, manual: bool) { let q = self.queue.read().unwrap(); let current = *self.current_track.read().unwrap(); - let repeat = *self.repeat.read().unwrap(); + let repeat = self.cfg.state().repeat; if repeat == RepeatSetting::RepeatTrack && !manual { if let Some(index) = current { @@ -311,7 +306,7 @@ impl Queue { pub fn previous(&self) { let q = self.queue.read().unwrap(); let current = *self.current_track.read().unwrap(); - let repeat = *self.repeat.read().unwrap(); + let repeat = self.cfg.state().repeat; if let Some(index) = self.previous_index() { self.play(index, false, false); @@ -332,18 +327,15 @@ impl Queue { } pub fn get_repeat(&self) -> RepeatSetting { - let repeat = self.repeat.read().unwrap(); - *repeat + self.cfg.state().repeat } pub fn set_repeat(&self, new: RepeatSetting) { - let mut repeat = self.repeat.write().unwrap(); - *repeat = new; + self.cfg.with_state_mut(|mut s| s.repeat = new); } pub fn get_shuffle(&self) -> bool { - let random_order = self.random_order.read().unwrap(); - random_order.is_some() + self.cfg.state().shuffle } fn generate_random_order(&self) { @@ -365,6 +357,7 @@ impl Queue { } pub fn set_shuffle(&self, new: bool) { + self.cfg.with_state_mut(|mut s| s.shuffle = new); if new { self.generate_random_order(); } else { diff --git a/src/spotify.rs b/src/spotify.rs index e0c7f1f..a530a00 100644 --- a/src/spotify.rs +++ b/src/spotify.rs @@ -47,7 +47,6 @@ use core::task::Poll; use std::pin::Pin; use std::str::FromStr; -use std::sync::atomic::{AtomicU16, Ordering}; use std::sync::{Arc, RwLock}; use std::thread; use std::time::{Duration, SystemTime}; @@ -57,7 +56,6 @@ use crate::artist::Artist; use crate::config; use crate::events::{Event, EventManager}; use crate::playable::Playable; -use crate::queue; use crate::track::Track; use rspotify::model::recommend::Recommendations; @@ -95,9 +93,6 @@ pub struct Spotify { channel: RwLock>>, user: Option, country: Option, - pub volume: AtomicU16, - pub repeat: queue::RepeatSetting, - pub shuffle: bool, } struct Worker { @@ -261,33 +256,10 @@ impl Spotify { credentials: Credentials, cfg: Arc, ) -> Spotify { - let volume = match &cfg.values().saved_state { - Some(state) => match state.volume { - Some(vol) => ((std::cmp::min(vol, 100) as f32) / 100.0 * 65535_f32).ceil() as u16, - None => 0xFFFF_u16, - }, - None => 0xFFFF_u16, - }; - let repeat = match &cfg.values().saved_state { - Some(state) => match &state.repeat { - Some(s) => match s.as_str() { - "track" => queue::RepeatSetting::RepeatTrack, - "playlist" => queue::RepeatSetting::RepeatPlaylist, - _ => queue::RepeatSetting::None, - }, - _ => queue::RepeatSetting::None, - }, - _ => queue::RepeatSetting::None, - }; - let shuffle = match &cfg.values().saved_state { - Some(state) => matches!(&state.shuffle, Some(true)), - None => false, - }; - let mut spotify = Spotify { events, credentials, - cfg, + cfg: cfg.clone(), status: RwLock::new(PlayerEvent::Stopped), api: RwLock::new(SpotifyAPI::default()), elapsed: RwLock::new(None), @@ -296,14 +268,12 @@ impl Spotify { channel: RwLock::new(None), user: None, country: None, - volume: AtomicU16::new(volume), - repeat, - shuffle, }; let (user_tx, user_rx) = oneshot::channel(); spotify.start_worker(Some(user_tx)); spotify.user = futures::executor::block_on(user_rx).ok(); + let volume = cfg.state().volume; spotify.set_volume(volume); spotify.country = spotify @@ -937,7 +907,7 @@ impl Spotify { } pub fn volume(&self) -> u16 { - self.volume.load(Ordering::Relaxed) as u16 + self.cfg.state().volume } fn log_scale(volume: u16) -> u16 { @@ -959,7 +929,7 @@ impl Spotify { pub fn set_volume(&self, volume: u16) { info!("setting volume to {}", volume); - self.volume.store(volume, Ordering::Relaxed); + self.cfg.with_state_mut(|mut s| s.volume = volume); self.send_worker(WorkerCommand::SetVolume(Self::log_scale(volume))); } }