docs: added lots of new internal documentation

This commit is contained in:
Tal 2024-09-27 14:21:49 +02:00
parent 93a668bae0
commit 8110b8418a
6 changed files with 75 additions and 16 deletions

View File

@ -1,3 +1,12 @@
#![warn(
clippy::all,
clippy::restriction,
clippy::pedantic,
clippy::nursery,
clippy::cargo
)]
#![allow(clippy::single_call_fn)]
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
mod play; mod play;

View File

@ -1,3 +1,5 @@
//! Responsible for the basic initialization & shutdown of the audio server & frontend.
use std::sync::Arc; use std::sync::Arc;
use tokio::{ use tokio::{
@ -8,6 +10,8 @@ use tokio::{
use crate::player::Player; use crate::player::Player;
use crate::player::{ui, Messages}; use crate::player::{ui, Messages};
/// Initializes the audio server, and then safely stops
/// it when the frontend quits.
pub async fn play() -> eyre::Result<()> { pub async fn play() -> eyre::Result<()> {
let (tx, rx) = mpsc::channel(8); let (tx, rx) = mpsc::channel(8);

View File

@ -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 std::{collections::VecDeque, sync::Arc};
use arc_swap::ArcSwapOption; use arc_swap::ArcSwapOption;
@ -18,8 +22,13 @@ pub mod ui;
/// Handles communication between the frontend & audio player. /// Handles communication between the frontend & audio player.
pub enum Messages { pub enum Messages {
/// Notifies the audio server that it should update the track.
Next, Next,
/// Similar to Next, but specific to the first track.
Init, Init,
/// Pauses the [Sink]. This will also unpause it if it is paused.
Pause, Pause,
} }
@ -28,15 +37,36 @@ const BUFFER_SIZE: usize = 5;
/// Main struct responsible for queuing up & playing tracks. /// Main struct responsible for queuing up & playing tracks.
pub struct Player { pub struct Player {
/// [rodio]'s [`Sink`] which can control playback.
pub sink: Sink, pub sink: Sink,
/// The [`TrackInfo`] of the current track.
/// This is [`None`] when lowfi is buffering.
pub current: ArcSwapOption<TrackInfo>, pub current: ArcSwapOption<TrackInfo>,
/// The tracks, which is a [VecDeque] that holds
/// *undecoded* [Track]s.
tracks: RwLock<VecDeque<Track>>, tracks: RwLock<VecDeque<Track>>,
/// The web client, which can contain a UserAgent & some
/// settings that help lowfi work more effectively.
client: Client, 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, _handle: OutputStreamHandle,
/// The [OutputStream], which is just here to keep the playback
/// alive and functioning.
_stream: OutputStream, _stream: OutputStream,
} }
/// SAFETY: This is necessary because [OutputStream] does not implement [Send],
/// SAFETY: even though it is perfectly possible.
unsafe impl Send for Player {} unsafe impl Send for Player {}
/// SAFETY: See implementation for [Send].
unsafe impl Sync for Player {} unsafe impl Sync for Player {}
impl Player { impl Player {
@ -55,6 +85,7 @@ impl Player {
}) })
} }
/// Just a shorthand for setting `current`.
async fn set_current(&self, info: TrackInfo) -> eyre::Result<()> { async fn set_current(&self, info: TrackInfo) -> eyre::Result<()> {
self.current.store(Some(Arc::new(info))); self.current.store(Some(Arc::new(info)));
@ -101,6 +132,7 @@ impl Player {
} }
}); });
// Start buffering tracks immediately.
itx.send(()).await?; itx.send(()).await?;
loop { loop {

View File

@ -23,18 +23,19 @@ fn format_duration(duration: &Duration) -> String {
format!("{:02}:{:02}", minutes, seconds) format!("{:02}:{:02}", minutes, seconds)
} }
enum Action { /// This represents the main "action" bars state.
enum ActionBar {
Paused(TrackInfo), Paused(TrackInfo),
Playing(TrackInfo), Playing(TrackInfo),
Loading, Loading,
} }
impl Action { impl ActionBar {
fn format(&self) -> (String, usize) { fn format(&self) -> (String, usize) {
let (word, subject) = match self { let (word, subject) = match self {
Action::Playing(x) => ("playing", Some(x.name.clone())), ActionBar::Playing(x) => ("playing", Some(x.name.clone())),
Action::Paused(x) => ("paused", Some(x.name.clone())), ActionBar::Paused(x) => ("paused", Some(x.name.clone())),
Action::Loading => ("loading", None), ActionBar::Loading => ("loading", None),
}; };
if let Some(subject) = subject { if let Some(subject) = subject {
@ -43,11 +44,12 @@ impl Action {
word.len() + 1 + subject.len(), word.len() + 1 + subject.len(),
) )
} else { } else {
(format!("{}", word), word.len()) (word.to_string(), word.len())
} }
} }
} }
/// The code for the interface itself.
async fn interface(queue: Arc<Player>) -> eyre::Result<()> { async fn interface(queue: Arc<Player>) -> eyre::Result<()> {
const WIDTH: usize = 27; const WIDTH: usize = 27;
const PROGRESS_WIDTH: usize = WIDTH - 16; const PROGRESS_WIDTH: usize = WIDTH - 16;
@ -58,12 +60,12 @@ async fn interface(queue: Arc<Player>) -> eyre::Result<()> {
let name = (*x.clone()).clone(); let name = (*x.clone()).clone();
if queue.sink.is_paused() { if queue.sink.is_paused() {
Action::Paused(name) ActionBar::Paused(name)
} else { } else {
Action::Playing(name) ActionBar::Playing(name)
} }
} }
None => Action::Loading, None => ActionBar::Loading,
} }
.format(); .format();
@ -88,7 +90,7 @@ async fn interface(queue: Arc<Player>) -> eyre::Result<()> {
let progress = format!( let progress = format!(
" [{}{}] {}/{} ", " [{}{}] {}/{} ",
"/".repeat(filled as usize), "/".repeat(filled),
" ".repeat(PROGRESS_WIDTH.saturating_sub(filled)), " ".repeat(PROGRESS_WIDTH.saturating_sub(filled)),
format_duration(&elapsed), format_duration(&elapsed),
format_duration(&duration), format_duration(&duration),
@ -118,6 +120,7 @@ async fn interface(queue: Arc<Player>) -> eyre::Result<()> {
} }
} }
/// Initializes the UI, this will also start taking input from the user.
pub async fn start(queue: Arc<Player>, sender: Sender<Messages>) -> eyre::Result<()> { pub async fn start(queue: Arc<Player>, sender: Sender<Messages>) -> eyre::Result<()> {
crossterm::terminal::enable_raw_mode()?; crossterm::terminal::enable_raw_mode()?;
crossterm::execute!(stderr(), Hide)?; crossterm::execute!(stderr(), Hide)?;

View File

@ -1,9 +1,11 @@
//! Has all of the functions for the `scrape` command.
use std::sync::LazyLock; use std::sync::LazyLock;
use futures::{stream::FuturesUnordered, StreamExt}; use futures::{stream::FuturesUnordered, StreamExt};
use scraper::{Html, Selector}; 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<Selector> = static SELECTOR: LazyLock<Selector> =
LazyLock::new(|| Selector::parse("html > body > pre > a").unwrap()); LazyLock::new(|| Selector::parse("html > body > pre > a").unwrap());
@ -48,7 +50,7 @@ async fn scan(extention: &str, include_full: bool) -> eyre::Result<Vec<String>>
let path = format!("{}/{}", year, month); let path = format!("{}/{}", year, month);
let items = parse(&path).await.unwrap(); let items = parse(&path).await.unwrap();
let items = items items
.into_iter() .into_iter()
.filter_map(|x| { .filter_map(|x| {
if x.ends_with(extention) { if x.ends_with(extention) {
@ -61,9 +63,7 @@ async fn scan(extention: &str, include_full: bool) -> eyre::Result<Vec<String>>
None None
} }
}) })
.collect::<Vec<String>>(); .collect::<Vec<String>>()
items
}); });
} }
} }

View File

@ -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 std::{io::Cursor, time::Duration};
use bytes::Bytes; use bytes::Bytes;
@ -70,7 +74,10 @@ impl TrackInfo {
/// This struct is seperate from [Track] since it is generated lazily from /// This struct is seperate from [Track] since it is generated lazily from
/// a track, and not when the track is first downloaded. /// a track, and not when the track is first downloaded.
pub struct DecodedTrack { pub struct DecodedTrack {
/// Has both the formatted name and some information from the decoded data.
pub info: TrackInfo, pub info: TrackInfo,
/// The decoded data, which is able to be played by [rodio].
pub data: DecodedData, pub data: DecodedData,
} }
@ -85,7 +92,11 @@ impl DecodedTrack {
/// The main track struct, which only includes data & the track name. /// The main track struct, which only includes data & the track name.
pub struct Track { pub struct Track {
/// This name is not formatted, and also includes the month & year of the track.
pub name: &'static str, pub name: &'static str,
/// The raw data of the track, which is not decoded and
/// therefore much more memory efficient.
pub data: Bytes, pub data: Bytes,
} }
@ -93,7 +104,7 @@ impl Track {
/// Fetches and downloads a random track from the tracklist. /// Fetches and downloads a random track from the tracklist.
pub async fn random(client: &Client) -> eyre::Result<Self> { pub async fn random(client: &Client) -> eyre::Result<Self> {
let name = random().await?; let name = random().await?;
let data = download(&name, client).await?; let data = download(name, client).await?;
Ok(Self { data, name }) Ok(Self { data, name })
} }