//! Persistent volume management. //! //! The module provides a tiny helper that reads and writes the user's //! configured volume to `volume.txt` inside the platform config directory. use std::{num::ParseIntError, path::PathBuf}; use tokio::fs; /// Shorthand for a [`Result`] with a persistent volume error. type Result = std::result::Result; /// Errors which occur when loading/unloading persistent volume. #[derive(Debug, thiserror::Error)] pub enum Error { #[error("couldn't find config directory")] DirectoryNotFound, #[error("io error: {0}")] Io(#[from] std::io::Error), #[error("error parsing volume integer: {0}")] Parse(#[from] ParseIntError), } /// Representation of the persistent volume stored on disk. /// /// The inner value is an integer percentage (0..=100). Use /// [`PersistentVolume::float`] to convert to a normalized `f32` in the /// range 0.0..=1.0 for playback volume calculations. #[derive(Clone, Copy)] pub struct PersistentVolume { /// The volume, as a percentage. pub(crate) inner: u16, } impl PersistentVolume { /// Retrieves the config file path, creating it if necessary. async fn path() -> Result { let config = dirs::config_dir() .ok_or(Error::DirectoryNotFound)? .join(PathBuf::from("lowfi")); if !config.exists() { fs::create_dir_all(&config).await?; } Ok(config.join(PathBuf::from("volume.txt"))) } /// Returns the volume as a normalized float in the range 0.0..=1.0. pub fn float(self) -> f32 { f32::from(self.inner) / 100.0 } /// Loads the [`PersistentVolume`] from the platform config directory. /// /// If the file does not exist a default of `100` is written and /// returned. pub async fn load() -> Result { let path = Self::path().await?; // Basically just read from the volume file if it exists, otherwise return 100. let volume = if path.exists() { let contents = fs::read_to_string(path).await?; let trimmed = contents.trim(); let stripped = trimmed.strip_suffix("%").unwrap_or(trimmed); stripped.parse()? } else { fs::write(&path, "100").await?; 100u16 }; Ok(Self { inner: volume }) } /// Saves `volume` (0.0..=1.0) to `volume.txt` as an integer percent. pub async fn save(volume: f32) -> Result<()> { let percentage = (volume * 100.0).abs().round() as u16; fs::write(Self::path().await?, percentage.to_string()).await?; Ok(()) } }