diff --git a/Cargo.lock b/Cargo.lock index 1801201..9f58f0c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2030,6 +2030,7 @@ dependencies = [ "reqwest", "rodio", "thiserror", + "urlencoding", ] [[package]] @@ -2797,6 +2798,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "urlencoding" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8db7427f936968176eaa7cdf81b7f98b980b18495ec28f1b5791ac3bfe3eea9" + [[package]] name = "vcpkg" version = "0.2.15" diff --git a/Cargo.toml b/Cargo.toml index 3cec750..cbabc06 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,13 +6,14 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -eframe = {version = "0.20.1"} -rodio = {version = "0.16.0"} -home = {version = "0.5.4"} -base64 = {version = "0.21.0"} -flate2 = {version = "1.0.25"} +eframe = "0.20.1" +rodio = "0.16.0" +home = "0.5.4" +base64 = "0.21.0" +flate2 = "1.0.25" gd_plist = {git = "https://github.com/Syudagye/gd-plist.git", version = "1.4.0"} -chrono = {version = "0.4.23"} -ordered-float = {version = "3.4.0"} -thiserror = {version = "1.0.38"} +chrono = "0.4.23" +ordered-float = "3.4.0" +thiserror = "1.0.38" reqwest = {version = "0.11.14", features = [ "blocking" ]} +urlencoding = "2.1.2" diff --git a/src/gd.rs b/src/gd.rs index a732634..ba39f86 100644 --- a/src/gd.rs +++ b/src/gd.rs @@ -21,6 +21,7 @@ pub enum Song { Unknown, } +#[derive(Clone)] pub struct SongResponse([Option; 9]); struct Level { @@ -34,16 +35,20 @@ pub struct OuterLevel { revision: Option, // k46 } -#[derive(Debug, Error)] +#[derive(Debug, Error, Clone)] pub enum SongRequestError { #[error("Request failed")] - ConnectionFailure(#[from] reqwest::Error), + ConnectionFailure, #[error("Index is not an int?????")] ParseFailure(#[from] ParseIntError), #[error("Not a Newgrounds song")] NotNewgrounds, } +impl From for SongRequestError { + fn from(_: reqwest::Error) -> Self {Self::ConnectionFailure} +} + impl Song { pub fn get_response(&self) -> Result { match self { @@ -60,7 +65,7 @@ impl Song { .split("~|~") .array_chunks() .try_for_each(|[id, value]| -> Result<(), SongRequestError> { - out.0[id.parse::()?] = Some(value.into()); + out.0[id.parse::()? - 1] = Some(value.into()); Ok(()) }) .map(|_| out) @@ -70,6 +75,36 @@ impl Song { } } +impl SongResponse { + pub fn id(&self) -> Option { + self.0[0].and_then(|s| s.parse().ok()) + } + pub fn name(&self) -> Option { + self.0[1] + } + pub fn artist_id(&self) -> Option { + self.0[2].and_then(|s| s.parse().ok()) + } + pub fn artist_name(&self) -> Option { + self.0[3] + } + pub fn size(&self) -> Option { + self.0[4].and_then(|s| s.parse().ok()) + } + pub fn video_id(&self) -> Option { + self.0[5] + } + pub fn youtube_url(&self) -> Option { + self.0[6] + } + pub fn song_priority(&self) -> Option { + self.0[8].and_then(|s| s.parse().ok()) + } + pub fn download_link(&self) -> Option { + self.0[9].and_then(|url| urlencoding::decode(&url).ok().map(|url| url.into_owned())) + } +} + impl OuterLevel { pub fn load_all() -> Vec { let plist = get_local_level_plist(); diff --git a/src/main.rs b/src/main.rs index 94f9b3a..8528581 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,7 +8,9 @@ use eframe::egui; use std::boxed::Box; use std::collections::VecDeque; use std::fs::File; +use std::io::Write; use thiserror::Error; +use reqwest::blocking as req; struct PipeDash { msg_queue: VecDeque, @@ -59,12 +61,16 @@ struct Song { enum SongError { #[error("Not a Newgrounds song")] NotNewgrounds, - #[error("Song mp3 not downloaded")] + #[error("Song mp3 couldn't be downloaded")] MissingFile(#[from] std::io::Error), #[error("Couldn't decode mp3 file")] BrokenSong(#[from] rodio::decoder::DecoderError), #[error("Couldn't access song data on servers")] - ServerError(#[from] gd::SongRequestError) + GdServerError(#[from] gd::SongRequestError), + #[error("Couldn't fetch song from Newgrounds")] + NgServerError(#[from] reqwest::Error), + #[error("Missing download link")] + MissingLink, } struct BeatRateWidget<'a> { @@ -105,19 +111,31 @@ impl Song { pub fn try_new(gd_song: gd::Song) -> Result { match &gd_song { gd::Song::Newgrounds { id } => { - let song_data = gd_song.get_response()?; - file::open(gd::gd_path().join(format!("{}.mp3", id))) - .or_else(|_| { - - }) - todo!("if file is missing, try to download it from the SongResponse. If that fails, return an error. Otherwise, return file info") - // Ok(Song { - // name: todo!(), - // id, - // decoder: rodio::Decoder::new_mp3(file)? - // }) - }, - _ => Err(SongError::NotNewgrounds) + let song_response = gd_song.get_response(); + let song_path = gd::gd_path().join(format!("{}.mp3", id)); + File::open(&song_path) + .or_else(|_| song_response + .clone() + .map_err(|e| e.into()) + .and_then(|response: gd::SongResponse| -> Result { + let song_blob = response.download_link() + .ok_or(SongError::MissingLink) + .and_then(|link| Ok(req::get(link)?.bytes()?))?; + let mut file = File::open(&song_path)?; + file.write(&song_blob); + Ok(file) + }) + ) + .and_then(|file| { + let name = song_response + .ok() + .and_then(|response| response.name()) + .unwrap_or("".into()); + let decoder = rodio::Decoder::new_mp3(file)?; + Ok(Song {name, id: *id, decoder}) + }) + } + _ => Err(SongError::NotNewgrounds), } } }