mirror of
https://github.com/talwat/lowfi
synced 2024-12-27 03:31:55 +00:00
docs: added lots of new internal documentation
This commit is contained in:
parent
93a668bae0
commit
8110b8418a
@ -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;
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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)?;
|
||||||
|
@ -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
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 })
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user