Skip to content

Commit

Permalink
Decouple configs from application's state (#422)
Browse files Browse the repository at this point in the history
## Changes
- remove `configs: Configs` from application's `State` struct
- add `config::CONFIGS` as a global static variable representing the application's configs
- implement `config::get_config` to get a static reference of `config::CONFIGS` and `config::set_config` to set the global variable
  • Loading branch information
aome510 authored Apr 21, 2024
1 parent 6a5e5bd commit d91318e
Show file tree
Hide file tree
Showing 17 changed files with 193 additions and 263 deletions.
4 changes: 2 additions & 2 deletions spotify_player/src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use librespot_core::{
session::{Session, SessionError},
};

use crate::state;
use crate::config;

#[derive(Clone)]
pub struct AuthConfig {
Expand All @@ -26,7 +26,7 @@ impl Default for AuthConfig {
}

impl AuthConfig {
pub fn new(configs: &state::Configs) -> Result<AuthConfig> {
pub fn new(configs: &config::Configs) -> Result<AuthConfig> {
let audio_cache_folder = if configs.app_config.device.audio_cache {
Some(configs.cache_folder.join("audio"))
} else {
Expand Down
7 changes: 4 additions & 3 deletions spotify_player/src/cli/handlers.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
auth::{new_session, new_session_with_new_creds, AuthConfig},
client, state,
client,
};

use super::*;
Expand Down Expand Up @@ -142,7 +142,7 @@ fn handle_playback_subcommand(args: &ArgMatches) -> Result<Request> {
/// to the client via a UDP socket.
/// If no running client found, create a new client running in a separate thread to
/// handle the socket request.
fn try_connect_to_client(socket: &UdpSocket, configs: &state::Configs) -> Result<()> {
fn try_connect_to_client(socket: &UdpSocket, configs: &config::Configs) -> Result<()> {
let port = configs.app_config.client_port;
socket.connect(("127.0.0.1", port))?;

Expand Down Expand Up @@ -175,8 +175,9 @@ fn try_connect_to_client(socket: &UdpSocket, configs: &state::Configs) -> Result
Ok(())
}

pub fn handle_cli_subcommand(cmd: &str, args: &ArgMatches, configs: &state::Configs) -> Result<()> {
pub fn handle_cli_subcommand(cmd: &str, args: &ArgMatches) -> Result<()> {
let socket = UdpSocket::bind("127.0.0.1:0")?;
let configs = config::get_config();

// handle commands that don't require a client separately
match cmd {
Expand Down
8 changes: 5 additions & 3 deletions spotify_player/src/client/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use anyhow::Context;
use rspotify::model::PlayableItem;
use tracing::Instrument;

use crate::state::*;
use crate::{config, state::*};

#[cfg(feature = "lyric-finder")]
use crate::utils::map_join;
Expand Down Expand Up @@ -186,13 +186,15 @@ pub async fn start_player_event_watchers(
state: SharedState,
client_pub: flume::Sender<ClientRequest>,
) {
let configs = config::get_config();

// Start a watcher task that updates the playback every `playback_refresh_duration_in_ms` ms.
// A positive value of `playback_refresh_duration_in_ms` is required to start the watcher.
if state.configs.app_config.playback_refresh_duration_in_ms > 0 {
if configs.app_config.playback_refresh_duration_in_ms > 0 {
tokio::task::spawn({
let client_pub = client_pub.clone();
let playback_refresh_duration = std::time::Duration::from_millis(
state.configs.app_config.playback_refresh_duration_in_ms,
configs.app_config.playback_refresh_duration_in_ms,
);
async move {
loop {
Expand Down
47 changes: 24 additions & 23 deletions spotify_player/src/client/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::ops::Deref;
use std::{borrow::Cow, collections::HashMap, sync::Arc};

use crate::config;
use crate::{auth::AuthConfig, state::*};

use anyhow::Context as _;
Expand Down Expand Up @@ -275,7 +276,7 @@ impl Client {
let playlists = self.current_user_playlists().await?;
store_data_into_file_cache(
FileCacheKey::Playlists,
&state.configs.cache_folder,
&config::get_config().cache_folder,
&playlists,
)
.context("store user's playlists into the cache folder")?;
Expand All @@ -285,7 +286,7 @@ impl Client {
let artists = self.current_user_followed_artists().await?;
store_data_into_file_cache(
FileCacheKey::FollowedArtists,
&state.configs.cache_folder,
&config::get_config().cache_folder,
&artists,
)
.context("store user's followed artists into the cache folder")?;
Expand All @@ -295,7 +296,7 @@ impl Client {
let albums = self.current_user_saved_albums().await?;
store_data_into_file_cache(
FileCacheKey::SavedAlbums,
&state.configs.cache_folder,
&config::get_config().cache_folder,
&albums,
)
.context("store user's saved albums into the cache folder")?;
Expand Down Expand Up @@ -323,7 +324,7 @@ impl Client {
.collect::<HashMap<_, _>>();
store_data_into_file_cache(
FileCacheKey::SavedTracks,
&state.configs.cache_folder,
&config::get_config().cache_folder,
&tracks_hm,
)
.context("store user's saved tracks into the cache folder")?;
Expand Down Expand Up @@ -490,7 +491,7 @@ impl Client {
for _ in 0..10 {
tokio::time::sleep(delay).await;

let id = match self.find_available_device(state).await {
let id = match self.find_available_device().await {
Ok(Some(id)) => Some(Cow::Owned(id)),
Ok(None) => None,
Err(err) => {
Expand Down Expand Up @@ -555,7 +556,7 @@ impl Client {
}

/// Find an available device. If found, return the device's ID.
async fn find_available_device(&self, state: &SharedState) -> Result<Option<String>> {
async fn find_available_device(&self) -> Result<Option<String>> {
let devices = self.device().await?.into_iter().collect::<Vec<_>>();
if devices.is_empty() {
tracing::warn!("No device found. Please make sure you already setup Spotify Connect \
Expand All @@ -570,6 +571,8 @@ impl Client {
.filter_map(|d| d.id.map(|id| (d.name, id)))
.collect::<Vec<_>>();

let configs = config::get_config();

// Manually append the integrated device to the device list if `streaming` feature is enabled.
// The integrated device may not show up in the device list returned by the Spotify API because
// 1. The device is just initialized and hasn't been registered in Spotify server.
Expand All @@ -581,7 +584,7 @@ impl Client {
{
let session = self.session().await;
devices.push((
state.configs.app_config.device.name.clone(),
configs.app_config.device.name.clone(),
session.device_id().to_string(),
));
}
Expand All @@ -594,7 +597,7 @@ impl Client {
// otherwise, use the first available device.
let id = devices
.iter()
.position(|d| d.0 == state.configs.app_config.default_device)
.position(|d| d.0 == configs.app_config.default_device)
.unwrap_or_default();

Ok(Some(devices.remove(id).1))
Expand Down Expand Up @@ -1339,6 +1342,8 @@ impl Client {
// Handle new track event
#[cfg(any(feature = "image", feature = "notify"))]
async fn handle_new_track_event(&self, state: &SharedState) -> Result<()> {
let configs = config::get_config();

let track = match state.player.read().current_playing_track() {
None => return Ok(()),
Some(track) => track.clone(),
Expand All @@ -1355,16 +1360,12 @@ impl Client {
crate::utils::map_join(&track.album.artists, |a| &a.name, ", ")
))
.replace('/', ""); // remove invalid characters from the file's name
let path = state.configs.cache_folder.join("image").join(path);
let path = configs.cache_folder.join("image").join(path);

#[cfg(feature = "image")]
if !state.data.read().caches.images.contains_key(url) {
let bytes = self
.retrieve_image(
url,
&path,
state.configs.app_config.enable_cover_image_cache,
)
.retrieve_image(url, &path, configs.app_config.enable_cover_image_cache)
.await?;
let image =
image::load_from_memory(&bytes).context("Failed to load image from memory")?;
Expand All @@ -1378,14 +1379,13 @@ impl Client {

// notify user about the playback's change if any
#[cfg(feature = "notify")]
if state.configs.app_config.enable_notify {
if configs.app_config.enable_notify {
// for Linux, ensure that the cached cover image is available to render the notification's thumbnail
#[cfg(all(unix, not(target_os = "macos")))]
self.retrieve_image(url, &path, true).await?;

if !state.configs.app_config.notify_streaming_only || self.stream_conn.lock().is_some()
{
Self::notify_new_track(track, &path, state)?;
if !configs.app_config.notify_streaming_only || self.stream_conn.lock().is_some() {
Self::notify_new_track(track, &path)?;
}
#[cfg(not(feature = "streaming"))]
Self::notify_new_track(track, &path, state)?;
Expand Down Expand Up @@ -1428,7 +1428,6 @@ impl Client {
fn notify_new_track(
track: rspotify_model::FullTrack,
cover_img_path: &std::path::Path,
state: &SharedState,
) -> Result<()> {
let mut n = notify_rust::Notification::new();

Expand Down Expand Up @@ -1464,17 +1463,19 @@ impl Client {
text
};

let configs = config::get_config();

n.appname("spotify_player")
.icon(cover_img_path.to_str().unwrap())
.summary(&get_text_from_format_str(
&state.configs.app_config.notify_format.summary,
&configs.app_config.notify_format.summary,
))
.body(&get_text_from_format_str(
&state.configs.app_config.notify_format.body,
&configs.app_config.notify_format.body,
));
if state.configs.app_config.notify_timeout_in_secs > 0 {
if configs.app_config.notify_timeout_in_secs > 0 {
n.timeout(std::time::Duration::from_secs(
state.configs.app_config.notify_timeout_in_secs,
configs.app_config.notify_timeout_in_secs,
));
}
n.show()?;
Expand Down
42 changes: 39 additions & 3 deletions spotify_player/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,36 @@ use config_parser2::*;
use librespot_core::config::SessionConfig;
use reqwest::Url;
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
use std::{
path::{Path, PathBuf},
sync::OnceLock,
};

pub use keymap::*;
pub use theme::*;
use keymap::*;
use theme::*;

pub use theme::Theme;

static CONFIGS: OnceLock<Configs> = OnceLock::new();

#[derive(Debug)]
pub struct Configs {
pub app_config: AppConfig,
pub keymap_config: KeymapConfig,
pub theme_config: ThemeConfig,
pub cache_folder: std::path::PathBuf,
}

impl Configs {
pub fn new(config_folder: &std::path::Path, cache_folder: &std::path::Path) -> Result<Self> {
Ok(Self {
app_config: AppConfig::new(config_folder)?,
keymap_config: KeymapConfig::new(config_folder)?,
theme_config: ThemeConfig::new(config_folder)?,
cache_folder: cache_folder.to_path_buf(),
})
}
}

#[derive(Debug, Deserialize, Serialize, ConfigParse)]
/// Application configurations
Expand Down Expand Up @@ -337,3 +363,13 @@ pub fn get_cache_folder_path() -> Result<PathBuf> {
None => Err(anyhow!("cannot find the $HOME folder")),
}
}

#[inline(always)]
pub fn get_config() -> &'static Configs {
CONFIGS.get().expect("configs is already initialized")
}
pub fn set_config(configs: Configs) {
CONFIGS
.set(configs)
.expect("configs should be initialized only once")
}
14 changes: 5 additions & 9 deletions spotify_player/src/event/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::{
client::{ClientRequest, PlayerRequest},
command::{self, Command},
config,
key::{Key, KeySequence},
state::*,
ui::single_line_input::LineInput,
Expand Down Expand Up @@ -87,9 +88,8 @@ fn handle_key_event(

// check if the current key sequence matches any keymap's prefix
// if not, reset the key sequence
if state
.configs
.keymap_config
let keymap_config = &config::get_config().keymap_config;
if keymap_config
.find_matched_prefix_keymaps(&key_sequence)
.is_empty()
{
Expand All @@ -107,11 +107,7 @@ fn handle_key_event(

// if the key sequence is not handled, let the global command handler handle it
let handled = if !handled {
match state
.configs
.keymap_config
.find_command_from_key_sequence(&key_sequence)
{
match keymap_config.find_command_from_key_sequence(&key_sequence) {
Some(command) => handle_global_command(command, client_pub, state, &mut ui)?,
None => false,
}
Expand Down Expand Up @@ -363,7 +359,7 @@ fn handle_global_command(
}
Command::SwitchTheme => {
// get the available themes with the current theme moved to the first position
let mut themes = state.configs.theme_config.themes.clone();
let mut themes = config::get_config().theme_config.themes.clone();
let id = themes.iter().position(|t| t.name == ui.theme.name);
if let Some(id) = id {
let theme = themes.remove(id);
Expand Down
Loading

0 comments on commit d91318e

Please sign in to comment.