diff --git a/src/main.rs b/src/main.rs index aba3a68..a6057b0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,12 @@ +#![warn( + clippy::all, + clippy::restriction, + clippy::pedantic, + clippy::nursery, + clippy::cargo +)] +#![allow(clippy::single_call_fn)] + use clap::{Parser, Subcommand}; mod play; diff --git a/src/play.rs b/src/play.rs index 8350058..fbdb113 100644 --- a/src/play.rs +++ b/src/play.rs @@ -1,3 +1,5 @@ +//! Responsible for the basic initialization & shutdown of the audio server & frontend. + use std::sync::Arc; use tokio::{ @@ -8,6 +10,8 @@ use tokio::{ use crate::player::Player; use crate::player::{ui, Messages}; +/// Initializes the audio server, and then safely stops +/// it when the frontend quits. pub async fn play() -> eyre::Result<()> { let (tx, rx) = mpsc::channel(8); diff --git a/src/player.rs b/src/player.rs index ecefa2b..82cca4b 100644 --- a/src/player.rs +++ b/src/player.rs @@ -1,3 +1,7 @@ +//! Responsible for playing & queueing audio. +//! This also has the code for the underlying +//! audio server which adds new tracks. + use std::{collections::VecDeque, sync::Arc}; use arc_swap::ArcSwapOption; @@ -18,8 +22,13 @@ pub mod ui; /// Handles communication between the frontend & audio player. pub enum Messages { + /// Notifies the audio server that it should update the track. Next, + + /// Similar to Next, but specific to the first track. Init, + + /// Pauses the [Sink]. This will also unpause it if it is paused. Pause, } @@ -28,15 +37,36 @@ const BUFFER_SIZE: usize = 5; /// Main struct responsible for queuing up & playing tracks. pub struct Player { + /// [rodio]'s [`Sink`] which can control playback. pub sink: Sink, + + /// The [`TrackInfo`] of the current track. + /// This is [`None`] when lowfi is buffering. pub current: ArcSwapOption, + + /// The tracks, which is a [VecDeque] that holds + /// *undecoded* [Track]s. tracks: RwLock>, + + /// The web client, which can contain a UserAgent & some + /// settings that help lowfi work more effectively. client: Client, + + /// The [OutputStreamHandle], which also can control some + /// playback, is for now unused and is here just to keep it + /// alive so the playback can function properly. _handle: OutputStreamHandle, + + /// The [OutputStream], which is just here to keep the playback + /// alive and functioning. _stream: OutputStream, } +/// SAFETY: This is necessary because [OutputStream] does not implement [Send], +/// SAFETY: even though it is perfectly possible. unsafe impl Send for Player {} + +/// SAFETY: See implementation for [Send]. unsafe impl Sync for Player {} impl Player { @@ -55,6 +85,7 @@ impl Player { }) } + /// Just a shorthand for setting `current`. async fn set_current(&self, info: TrackInfo) -> eyre::Result<()> { self.current.store(Some(Arc::new(info))); @@ -101,6 +132,7 @@ impl Player { } }); + // Start buffering tracks immediately. itx.send(()).await?; loop { diff --git a/src/player/ui.rs b/src/player/ui.rs index 9b73f2c..b0cde6a 100644 --- a/src/player/ui.rs +++ b/src/player/ui.rs @@ -23,18 +23,19 @@ fn format_duration(duration: &Duration) -> String { format!("{:02}:{:02}", minutes, seconds) } -enum Action { +/// This represents the main "action" bars state. +enum ActionBar { Paused(TrackInfo), Playing(TrackInfo), Loading, } -impl Action { +impl ActionBar { fn format(&self) -> (String, usize) { let (word, subject) = match self { - Action::Playing(x) => ("playing", Some(x.name.clone())), - Action::Paused(x) => ("paused", Some(x.name.clone())), - Action::Loading => ("loading", None), + ActionBar::Playing(x) => ("playing", Some(x.name.clone())), + ActionBar::Paused(x) => ("paused", Some(x.name.clone())), + ActionBar::Loading => ("loading", None), }; if let Some(subject) = subject { @@ -43,11 +44,12 @@ impl Action { word.len() + 1 + subject.len(), ) } else { - (format!("{}", word), word.len()) + (word.to_string(), word.len()) } } } +/// The code for the interface itself. async fn interface(queue: Arc) -> eyre::Result<()> { const WIDTH: usize = 27; const PROGRESS_WIDTH: usize = WIDTH - 16; @@ -58,12 +60,12 @@ async fn interface(queue: Arc) -> eyre::Result<()> { let name = (*x.clone()).clone(); if queue.sink.is_paused() { - Action::Paused(name) + ActionBar::Paused(name) } else { - Action::Playing(name) + ActionBar::Playing(name) } } - None => Action::Loading, + None => ActionBar::Loading, } .format(); @@ -88,7 +90,7 @@ async fn interface(queue: Arc) -> eyre::Result<()> { let progress = format!( " [{}{}] {}/{} ", - "/".repeat(filled as usize), + "/".repeat(filled), " ".repeat(PROGRESS_WIDTH.saturating_sub(filled)), format_duration(&elapsed), format_duration(&duration), @@ -118,6 +120,7 @@ async fn interface(queue: Arc) -> eyre::Result<()> { } } +/// Initializes the UI, this will also start taking input from the user. pub async fn start(queue: Arc, sender: Sender) -> eyre::Result<()> { crossterm::terminal::enable_raw_mode()?; crossterm::execute!(stderr(), Hide)?; diff --git a/src/scrape.rs b/src/scrape.rs index bd7ab8d..f38b163 100644 --- a/src/scrape.rs +++ b/src/scrape.rs @@ -1,9 +1,11 @@ +//! Has all of the functions for the `scrape` command. + use std::sync::LazyLock; use futures::{stream::FuturesUnordered, StreamExt}; use scraper::{Html, Selector}; -const BASE_URL: &'static str = "https://lofigirl.com/wp-content/uploads/"; +const BASE_URL: &str = "https://lofigirl.com/wp-content/uploads/"; static SELECTOR: LazyLock = LazyLock::new(|| Selector::parse("html > body > pre > a").unwrap()); @@ -48,7 +50,7 @@ async fn scan(extention: &str, include_full: bool) -> eyre::Result> let path = format!("{}/{}", year, month); let items = parse(&path).await.unwrap(); - let items = items + items .into_iter() .filter_map(|x| { if x.ends_with(extention) { @@ -61,9 +63,7 @@ async fn scan(extention: &str, include_full: bool) -> eyre::Result> None } }) - .collect::>(); - - items + .collect::>() }); } } diff --git a/src/tracks.rs b/src/tracks.rs index efb2087..466873a 100644 --- a/src/tracks.rs +++ b/src/tracks.rs @@ -1,3 +1,7 @@ +//! Has all of the structs for managing the state +//! of tracks, as well as downloading them & +//! finding new ones. + use std::{io::Cursor, time::Duration}; use bytes::Bytes; @@ -70,7 +74,10 @@ impl TrackInfo { /// This struct is seperate from [Track] since it is generated lazily from /// a track, and not when the track is first downloaded. pub struct DecodedTrack { + /// Has both the formatted name and some information from the decoded data. pub info: TrackInfo, + + /// The decoded data, which is able to be played by [rodio]. pub data: DecodedData, } @@ -85,7 +92,11 @@ impl DecodedTrack { /// The main track struct, which only includes data & the track name. pub struct Track { + /// This name is not formatted, and also includes the month & year of the track. pub name: &'static str, + + /// The raw data of the track, which is not decoded and + /// therefore much more memory efficient. pub data: Bytes, } @@ -93,7 +104,7 @@ impl Track { /// Fetches and downloads a random track from the tracklist. pub async fn random(client: &Client) -> eyre::Result { let name = random().await?; - let data = download(&name, client).await?; + let data = download(name, client).await?; Ok(Self { data, name }) }