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}; use clap::{Parser, Subcommand};
mod player;
mod scrape; mod scrape;
mod tracks; mod tracks;
mod player;
/// An extremely simple lofi player. /// An extremely simple lofi player.
#[derive(Parser)] #[derive(Parser)]
@ -17,7 +17,7 @@ enum Commands {
/// Scrapes the lofi girl website file server for mp3 files. /// Scrapes the lofi girl website file server for mp3 files.
Scrape, Scrape,
/// Starts the player. /// Starts the player.
Play Play,
} }
#[tokio::main] #[tokio::main]
@ -26,6 +26,6 @@ async fn main() -> eyre::Result<()> {
match cli.command { match cli.command {
Commands::Scrape => scrape::scrape().await, 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 reqwest::Client;
use rodio::{Decoder, OutputStream, Sink}; use rodio::{Decoder, OutputStream, Sink};
use tokio::{ use tokio::{
select, sync::{ select,
sync::{
mpsc::{self, Receiver}, mpsc::{self, Receiver},
RwLock, RwLock,
}, task },
task,
}; };
/// The amount of songs to buffer up. /// The amount of songs to buffer up.
@ -17,6 +23,7 @@ use crate::tracks::Track;
/// Handles communication between the frontend & audio player. /// Handles communication between the frontend & audio player.
pub enum Messages { pub enum Messages {
Skip, Skip,
Die,
} }
/// Main struct responsible for queuing up tracks. /// Main struct responsible for queuing up tracks.
@ -69,7 +76,7 @@ impl Queue {
self, self,
sink: Sink, sink: Sink,
client: Client, client: Client,
mut rx: Receiver<Messages> mut rx: Receiver<Messages>,
) -> eyre::Result<()> { ) -> eyre::Result<()> {
let sink = Arc::new(sink); let sink = Arc::new(sink);
@ -89,11 +96,21 @@ impl Queue {
let track = self.next(&client).await?; let track = self.next(&client).await?;
sink.append(Decoder::new(track.data)?); 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<()> { pub async fn play() -> eyre::Result<()> {
let queue = Queue::new().await; let queue = Queue::new().await;
let (tx, rx) = mpsc::channel(8); let (tx, rx) = mpsc::channel(8);
@ -106,11 +123,15 @@ pub async fn play() -> eyre::Result<()> {
crossterm::terminal::enable_raw_mode()?; crossterm::terminal::enable_raw_mode()?;
gui().await?;
'a: loop { 'a: loop {
match crossterm::event::read()? { match crossterm::event::read()? {
crossterm::event::Event::Key(event) => match event.code { crossterm::event::Event::Key(event) => match event.code {
crossterm::event::KeyCode::Char(x) => { crossterm::event::KeyCode::Char(x) => {
if x == 'q' { if x == 'q' {
tx.send(Messages::Die).await?;
break 'a; break 'a;
} else if x == 's' { } else if x == 's' {
tx.send(Messages::Skip).await?; tx.send(Messages::Skip).await?;

View File

@ -3,16 +3,20 @@ use std::sync::LazyLock;
use futures::{stream::FuturesUnordered, StreamExt}; use futures::{stream::FuturesUnordered, StreamExt};
use scraper::{Html, Selector}; use scraper::{Html, Selector};
static SELECTOR: LazyLock<Selector> = LazyLock::new(|| { static SELECTOR: LazyLock<Selector> =
Selector::parse("html > body > pre > a").unwrap() LazyLock::new(|| Selector::parse("html > body > pre > a").unwrap());
});
async fn parse(path: &str) -> eyre::Result<Vec<String>> { 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 document = response.text().await?;
let html = Html::parse_document(&document); 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. /// This function basically just scans the entire file server, and returns a list of paths to mp3 files.
@ -22,10 +26,13 @@ async fn parse(path: &str) -> eyre::Result<Vec<String>> {
async fn scan() -> eyre::Result<Vec<String>> { async fn scan() -> eyre::Result<Vec<String>> {
let items = parse("").await?; let items = parse("").await?;
let years: Vec<u32> = items.iter().filter_map(|x| { let years: Vec<u32> = items
let year = x.strip_suffix("/")?; .iter()
year.parse().ok() .filter_map(|x| {
}).collect(); let year = x.strip_suffix("/")?;
year.parse().ok()
})
.collect();
// A little bit of async to run all of the months concurrently. // A little bit of async to run all of the months concurrently.
let mut futures = FuturesUnordered::new(); let mut futures = FuturesUnordered::new();
@ -38,13 +45,16 @@ async fn scan() -> 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.into_iter().filter_map(|x| { let items = items
if x.ends_with(".mp3") { .into_iter()
Some(format!("{path}{x}")) .filter_map(|x| {
} else { if x.ends_with(".mp3") {
None Some(format!("{path}{x}"))
} } else {
}).collect::<Vec<String>>(); None
}
})
.collect::<Vec<String>>();
items items
}); });