chore: eliminate public statics

This commit is contained in:
Tal 2026-01-03 13:25:21 +01:00
parent 9acfcdcf9d
commit 6f6a552de5
13 changed files with 67 additions and 31 deletions

View File

@ -1,3 +1,5 @@
//! Some simple audio related utilities.
pub mod waiter; pub mod waiter;
/// This gets the output stream while also shutting up alsa with [libc]. /// This gets the output stream while also shutting up alsa with [libc].

View File

@ -1,3 +1,5 @@
//! All of the logic and state relating to the downloader.
use std::{ use std::{
sync::atomic::{self, AtomicBool, AtomicU8}, sync::atomic::{self, AtomicBool, AtomicU8},
time::Duration, time::Duration,
@ -13,14 +15,40 @@ use tokio::sync::mpsc;
/// indicate to the UI that a download is in progress. /// indicate to the UI that a download is in progress.
static LOADING: AtomicBool = AtomicBool::new(false); static LOADING: AtomicBool = AtomicBool::new(false);
/// Global download progress in the range 0..=100 updated atomically. /// Global download progress as an integer updated atomically.
/// ///
/// The UI can read this `AtomicU8` to render a global progress indicator /// This is just a [`AtomicU8`] from 0 to 255, really representing
/// when there isn't an immediately queued track available. /// a progress percentage as just a simple integer. For instance,
pub(crate) static PROGRESS: AtomicU8 = AtomicU8::new(0); /// 0.5 would be represented here as 127.
static PROGRESS: AtomicU8 = AtomicU8::new(0);
/// A convenient alias for the progress `AtomicU8` pointer type. /// A convenient wrapper for the global progress. This is updated by the downloader,
pub type Progress = &'static AtomicU8; /// and then accessed by the UI to display progress when there isn't an available
/// queued track.
#[derive(Clone, Copy, Debug)]
pub struct Progress(&'static AtomicU8);
impl Progress {
/// Creates a new handle to the global progress.
pub fn new() -> Self {
Self(&PROGRESS)
}
/// Sets the global progress.
///
/// `value` must be between 0 and 1.
pub fn set(&self, value: f32) {
self.0.store(
(value * f32::from(u8::MAX)).round() as u8,
atomic::Ordering::Relaxed,
);
}
/// Returns the global progress as a [`f32`] between 0 and 1.
pub fn get(&self) -> f32 {
f32::from(self.0.load(atomic::Ordering::Relaxed)) / f32::from(u8::MAX)
}
}
/// The downloader, which has all of the state necessary /// The downloader, which has all of the state necessary
/// to download tracks and add them to the queue. /// to download tracks and add them to the queue.
@ -57,7 +85,7 @@ impl Downloader {
loop { loop {
let result = self let result = self
.tracks .tracks
.random(&self.client, &PROGRESS, &mut self.rng) .random(&self.client, Progress::new(), &mut self.rng)
.await; .await;
match result { match result {
@ -106,7 +134,7 @@ impl Handle {
pub fn track(&mut self) -> Output { pub fn track(&mut self) -> Output {
self.queue.try_recv().map_or_else(|_| { self.queue.try_recv().map_or_else(|_| {
LOADING.store(true, atomic::Ordering::Relaxed); LOADING.store(true, atomic::Ordering::Relaxed);
Output::Loading(Some(&PROGRESS)) Output::Loading(Some(Progress::new()))
}, Output::Queued, }, Output::Queued,
) )
} }

View File

@ -1,11 +1,12 @@
//! An extremely simple lofi player. //! An extremely simple lofi player.
use crate::player::Player; use crate::player::Player;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use std::path::PathBuf; use std::path::PathBuf;
pub mod audio; pub mod audio;
pub mod bookmark; pub mod bookmark;
pub mod download; pub mod downloader;
pub mod error; pub mod error;
pub mod message; pub mod message;
pub mod player; pub mod player;

View File

@ -1,11 +1,12 @@
use std::sync::Arc; //! The player, which contains all of the core logic behind the music player.
use std::sync::Arc;
use tokio::sync::mpsc::{self, Receiver}; use tokio::sync::mpsc::{self, Receiver};
use crate::{ use crate::{
audio::waiter, audio::waiter,
bookmark::Bookmarks, bookmark::Bookmarks,
download, downloader,
tracks::{self, List}, tracks::{self, List},
ui, ui,
volume::PersistentVolume, volume::PersistentVolume,
@ -20,7 +21,7 @@ use crate::{
pub enum Current { pub enum Current {
/// Waiting for a track to arrive. The optional `Progress` is used to /// Waiting for a track to arrive. The optional `Progress` is used to
/// indicate global download progress when present. /// indicate global download progress when present.
Loading(Option<download::Progress>), Loading(Option<downloader::Progress>),
/// A decoded track that can be played; contains the track `Info`. /// A decoded track that can be played; contains the track `Info`.
Track(tracks::Info), Track(tracks::Info),
@ -52,7 +53,7 @@ pub struct Player {
current: Current, current: Current,
/// Background downloader that fills the internal queue. /// Background downloader that fills the internal queue.
downloader: download::Handle, downloader: downloader::Handle,
/// Receiver for incoming `Message` commands. /// Receiver for incoming `Message` commands.
rx: Receiver<crate::Message>, rx: Receiver<crate::Message>,
@ -158,10 +159,10 @@ impl Player {
self.sink.stop(); self.sink.stop();
match self.downloader.track() { match self.downloader.track() {
download::Output::Loading(progress) => { downloader::Output::Loading(progress) => {
self.set_current(Current::Loading(progress))?; self.set_current(Current::Loading(progress))?;
} }
download::Output::Queued(queued) => self.play(queued)?, downloader::Output::Queued(queued) => self.play(queued)?,
} }
} }
Message::Play => { Message::Play => {

View File

@ -1,3 +1,4 @@
//! Built in web scraping, which isn't guaranteed to have a unified UI.
#![allow(clippy::all)] #![allow(clippy::all)]
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};

View File

@ -1,7 +1,7 @@
//! Task management. //! Task management.
//! //!
//! This file aims to abstract a lot of annoying Rust async logic, which may be subject to change. //! This file aims to abstract a lot of potentially annoying Rust async logic, which may be
//! For those who are not intimately familiar with async rust, this will be very confusing. //! subject to change.
use futures_util::TryFutureExt; use futures_util::TryFutureExt;
use std::future::Future; use std::future::Future;

View File

@ -108,7 +108,7 @@ mod decoded {
#[cfg(test)] #[cfg(test)]
mod list { mod list {
use crate::{download::PROGRESS, tracks::List}; use crate::{downloader::Progress, tracks::List};
use reqwest::Client; use reqwest::Client;
#[test] #[test]
@ -172,7 +172,7 @@ mod list {
let client = Client::new(); let client = Client::new();
let track = list let track = list
.random(&client, &PROGRESS, &mut fastrand::Rng::new()) .random(&client, Progress::new(), &mut fastrand::Rng::new())
.await .await
.unwrap(); .unwrap();
assert_eq!(track.display, "Apple Juice"); assert_eq!(track.display, "Apple Juice");

View File

@ -125,7 +125,7 @@ mod interface {
use tokio::time::Instant; use tokio::time::Instant;
use crate::{ use crate::{
download::PROGRESS, downloader::Progress,
player::Current, player::Current,
tracks, tracks,
ui::{Interface, State}, ui::{Interface, State},
@ -175,9 +175,9 @@ mod interface {
#[tokio::test] #[tokio::test]
async fn progress() { async fn progress() {
let sink = Arc::new(rodio::Sink::new().0); let sink = Arc::new(rodio::Sink::new().0);
PROGRESS.store(50, std::sync::atomic::Ordering::Relaxed); Progress::new().set(0.5);
let mut state = State::initial(sink, String::from("test")); let mut state = State::initial(sink, String::from("test"));
state.current = Current::Loading(Some(&PROGRESS)); state.current = Current::Loading(Some(Progress::new()));
let menu = Interface::default().menu(&mut state); let menu = Interface::default().menu(&mut state);

View File

@ -1,10 +1,7 @@
//! The module containing all of the logic behind track lists, //! The module containing all of the logic behind track lists,
//! as well as obtaining track names & downloading the raw audio data //! as well as obtaining track names & downloading the raw audio data
use std::{ use std::cmp::min;
cmp::min,
sync::atomic::{AtomicU8, Ordering},
};
use bytes::{BufMut as _, Bytes, BytesMut}; use bytes::{BufMut as _, Bytes, BytesMut};
use futures_util::StreamExt as _; use futures_util::StreamExt as _;
@ -13,6 +10,7 @@ use tokio::fs;
use crate::{ use crate::{
data_dir, data_dir,
downloader::Progress,
tracks::{ tracks::{
self, self,
error::{self, WithTrackContext as _}, error::{self, WithTrackContext as _},
@ -70,7 +68,7 @@ impl List {
&self, &self,
track: &str, track: &str,
client: &Client, client: &Client,
progress: Option<&AtomicU8>, progress: Option<Progress>,
) -> tracks::Result<(Bytes, String)> { ) -> tracks::Result<(Bytes, String)> {
// If the track has a protocol, then we should ignore the base for it. // If the track has a protocol, then we should ignore the base for it.
let path = if track.contains("://") { let path = if track.contains("://") {
@ -114,8 +112,7 @@ impl List {
while let Some(item) = stream.next().await { while let Some(item) = stream.next().await {
let chunk = item.track(track)?; let chunk = item.track(track)?;
downloaded = min(downloaded + (chunk.len() as u64), total); downloaded = min(downloaded + (chunk.len() as u64), total);
let rounded = ((downloaded as f64) / (total as f64) * 100.0).round() as u8; progress.set(downloaded as f32 / total as f32);
progress.store(rounded, Ordering::Relaxed);
bytes.put(chunk); bytes.put(chunk);
} }
@ -133,7 +130,7 @@ impl List {
pub async fn random( pub async fn random(
&self, &self,
client: &Client, client: &Client,
progress: &AtomicU8, progress: Progress,
rng: &mut fastrand::Rng, rng: &mut fastrand::Rng,
) -> tracks::Result<Queued> { ) -> tracks::Result<Queued> {
let (path, display) = self.random_path(rng); let (path, display) = self.random_path(rng);

View File

@ -1,3 +1,5 @@
//! Everything which has to do with the user interface, including both input and output.
use std::sync::Arc; use std::sync::Arc;
use crate::player::Current; use crate::player::Current;

View File

@ -1,3 +1,5 @@
//! Contains the initialization and other handling of the terminal environment.
use std::{io::stdout, panic}; use std::{io::stdout, panic};
use crossterm::{ use crossterm::{

View File

@ -1,3 +1,5 @@
//! Responsible for the actual graphical interface of lowfi.
use crate::{ use crate::{
ui::{self, State}, ui::{self, State},
Args, Args,

View File

@ -111,7 +111,7 @@ impl ActionBar {
pub fn action(state: &ui::State, width: usize) -> String { pub fn action(state: &ui::State, width: usize) -> String {
let action = match state.current.clone() { let action = match state.current.clone() {
Current::Loading(progress) => { Current::Loading(progress) => {
ActionBar::Loading(progress.map(|x| x.load(std::sync::atomic::Ordering::Relaxed))) ActionBar::Loading(progress.map(|x| (x.get() * 100.0).round() as u8))
} }
Current::Track(info) => { Current::Track(info) => {
if state.sink.volume() < 0.01 { if state.sink.volume() < 0.01 {