feat: start work on frontend

This commit is contained in:
talwat 2024-09-25 18:11:42 +02:00
parent e0d13792e2
commit 681889a268
4 changed files with 62 additions and 31 deletions

View File

@ -1,8 +1,8 @@
use clap::{Parser, Subcommand};
mod player;
mod scrape;
mod tracks;
mod player;
/// An extremely simple lofi player.
#[derive(Parser)]
@ -17,7 +17,7 @@ enum Commands {
/// Scrapes the lofi girl website file server for mp3 files.
Scrape,
/// Starts the player.
Play
Play,
}
#[tokio::main]
@ -26,6 +26,6 @@ async fn main() -> eyre::Result<()> {
match cli.command {
Commands::Scrape => scrape::scrape().await,
Commands::Play => player::play().await
Commands::Play => player::play().await,
}
}
}

View File

@ -1,12 +1,18 @@
use std::{collections::VecDeque, sync::Arc};
use std::{collections::VecDeque, io::stderr, sync::Arc};
use crossterm::{
cursor::{MoveDown, MoveToColumn, MoveToNextLine},
style::Print,
};
use reqwest::Client;
use rodio::{Decoder, OutputStream, Sink};
use tokio::{
select, sync::{
select,
sync::{
mpsc::{self, Receiver},
RwLock,
}, task
},
task,
};
/// The amount of songs to buffer up.
@ -17,6 +23,7 @@ use crate::tracks::Track;
/// Handles communication between the frontend & audio player.
pub enum Messages {
Skip,
Die,
}
/// Main struct responsible for queuing up tracks.
@ -62,14 +69,14 @@ impl Queue {
}
/// This is the main "audio server".
///
///
/// `rx` is used to communicate with it, for example when to
/// skip tracks or pause.
pub async fn play(
self,
sink: Sink,
client: Client,
mut rx: Receiver<Messages>
mut rx: Receiver<Messages>,
) -> eyre::Result<()> {
let sink = Arc::new(sink);
@ -89,11 +96,21 @@ impl Queue {
let track = self.next(&client).await?;
sink.append(Decoder::new(track.data)?);
}
Messages::Die => break,
}
}
Ok(())
}
}
pub async fn gui() -> eyre::Result<()> {
crossterm::execute!(stderr(), MoveToColumn(0), Print("hello!\r\n"))?;
crossterm::execute!(stderr(), Print("next line!\r\n"))?;
Ok(())
}
pub async fn play() -> eyre::Result<()> {
let queue = Queue::new().await;
let (tx, rx) = mpsc::channel(8);
@ -106,11 +123,15 @@ pub async fn play() -> eyre::Result<()> {
crossterm::terminal::enable_raw_mode()?;
gui().await?;
'a: loop {
match crossterm::event::read()? {
crossterm::event::Event::Key(event) => match event.code {
crossterm::event::KeyCode::Char(x) => {
if x == 'q' {
tx.send(Messages::Die).await?;
break 'a;
} else if x == 's' {
tx.send(Messages::Skip).await?;

View File

@ -3,48 +3,58 @@ use std::sync::LazyLock;
use futures::{stream::FuturesUnordered, StreamExt};
use scraper::{Html, Selector};
static SELECTOR: LazyLock<Selector> = LazyLock::new(|| {
Selector::parse("html > body > pre > a").unwrap()
});
static SELECTOR: LazyLock<Selector> =
LazyLock::new(|| Selector::parse("html > body > pre > a").unwrap());
async fn parse(path: &str) -> eyre::Result<Vec<String>> {
let response = reqwest::get(format!("https://lofigirl.com/wp-content/uploads/{}", path)).await?;
let response =
reqwest::get(format!("https://lofigirl.com/wp-content/uploads/{}", path)).await?;
let document = response.text().await?;
let html = Html::parse_document(&document);
Ok(html.select(&SELECTOR).skip(5).map(|x| String::from(x.attr("href").unwrap())).collect())
Ok(html
.select(&SELECTOR)
.skip(5)
.map(|x| String::from(x.attr("href").unwrap()))
.collect())
}
/// This function basically just scans the entire file server, and returns a list of paths to mp3 files.
///
///
/// It's a bit hacky, and basically works by checking all of the years, then months, and then all of the files.
/// This is done as a way to avoid recursion, since async rust really hates recursive functions.
async fn scan() -> eyre::Result<Vec<String>> {
let items = parse("").await?;
let years: Vec<u32> = items.iter().filter_map(|x| {
let year = x.strip_suffix("/")?;
year.parse().ok()
}).collect();
let years: Vec<u32> = items
.iter()
.filter_map(|x| {
let year = x.strip_suffix("/")?;
year.parse().ok()
})
.collect();
// A little bit of async to run all of the months concurrently.
let mut futures = FuturesUnordered::new();
for year in years {
let months = parse(&year.to_string()).await?;
for month in months {
futures.push(async move {
let path = format!("{}/{}", year, month);
let items = parse(&path).await.unwrap();
let items = items.into_iter().filter_map(|x| {
if x.ends_with(".mp3") {
Some(format!("{path}{x}"))
} else {
None
}
}).collect::<Vec<String>>();
let items = items
.into_iter()
.filter_map(|x| {
if x.ends_with(".mp3") {
Some(format!("{path}{x}"))
} else {
None
}
})
.collect::<Vec<String>>();
items
});

View File

@ -10,7 +10,7 @@ async fn download(track: &str, client: &Client) -> eyre::Result<Data> {
let url = format!("https://lofigirl.com/wp-content/uploads/{}", track);
let response = client.get(url).send().await?;
let file = Cursor::new(response.bytes().await?);
Ok(file)
}
@ -37,4 +37,4 @@ impl Track {
Ok(Self { name, data })
}
}
}