diff --git a/src/main.rs b/src/main.rs index 8e257b0..732f87c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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, } -} \ No newline at end of file +} diff --git a/src/player.rs b/src/player.rs index ef2171e..2577811 100644 --- a/src/player.rs +++ b/src/player.rs @@ -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 + mut rx: Receiver, ) -> 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?; diff --git a/src/scrape.rs b/src/scrape.rs index b54468b..3e47492 100644 --- a/src/scrape.rs +++ b/src/scrape.rs @@ -3,48 +3,58 @@ use std::sync::LazyLock; use futures::{stream::FuturesUnordered, StreamExt}; use scraper::{Html, Selector}; -static SELECTOR: LazyLock = LazyLock::new(|| { - Selector::parse("html > body > pre > a").unwrap() -}); +static SELECTOR: LazyLock = + LazyLock::new(|| Selector::parse("html > body > pre > a").unwrap()); async fn parse(path: &str) -> eyre::Result> { - 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> { let items = parse("").await?; - - let years: Vec = items.iter().filter_map(|x| { - let year = x.strip_suffix("/")?; - year.parse().ok() - }).collect(); + + let years: Vec = 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::>(); + let items = items + .into_iter() + .filter_map(|x| { + if x.ends_with(".mp3") { + Some(format!("{path}{x}")) + } else { + None + } + }) + .collect::>(); items }); diff --git a/src/tracks.rs b/src/tracks.rs index 56fea32..3c4bd0d 100644 --- a/src/tracks.rs +++ b/src/tracks.rs @@ -10,7 +10,7 @@ async fn download(track: &str, client: &Client) -> eyre::Result { 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 }) } -} \ No newline at end of file +}