loading a new Song

main
aprzn 2 years ago
parent 441cc8cb24
commit e865b51c73

7
Cargo.lock generated

@ -2030,6 +2030,7 @@ dependencies = [
"reqwest", "reqwest",
"rodio", "rodio",
"thiserror", "thiserror",
"urlencoding",
] ]
[[package]] [[package]]
@ -2797,6 +2798,12 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "urlencoding"
version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8db7427f936968176eaa7cdf81b7f98b980b18495ec28f1b5791ac3bfe3eea9"
[[package]] [[package]]
name = "vcpkg" name = "vcpkg"
version = "0.2.15" version = "0.2.15"

@ -6,13 +6,14 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
eframe = {version = "0.20.1"} eframe = "0.20.1"
rodio = {version = "0.16.0"} rodio = "0.16.0"
home = {version = "0.5.4"} home = "0.5.4"
base64 = {version = "0.21.0"} base64 = "0.21.0"
flate2 = {version = "1.0.25"} flate2 = "1.0.25"
gd_plist = {git = "https://github.com/Syudagye/gd-plist.git", version = "1.4.0"} gd_plist = {git = "https://github.com/Syudagye/gd-plist.git", version = "1.4.0"}
chrono = {version = "0.4.23"} chrono = "0.4.23"
ordered-float = {version = "3.4.0"} ordered-float = "3.4.0"
thiserror = {version = "1.0.38"} thiserror = "1.0.38"
reqwest = {version = "0.11.14", features = [ "blocking" ]} reqwest = {version = "0.11.14", features = [ "blocking" ]}
urlencoding = "2.1.2"

@ -21,6 +21,7 @@ pub enum Song {
Unknown, Unknown,
} }
#[derive(Clone)]
pub struct SongResponse([Option<String>; 9]); pub struct SongResponse([Option<String>; 9]);
struct Level { struct Level {
@ -34,16 +35,20 @@ pub struct OuterLevel {
revision: Option<i64>, // k46 revision: Option<i64>, // k46
} }
#[derive(Debug, Error)] #[derive(Debug, Error, Clone)]
pub enum SongRequestError { pub enum SongRequestError {
#[error("Request failed")] #[error("Request failed")]
ConnectionFailure(#[from] reqwest::Error), ConnectionFailure,
#[error("Index is not an int?????")] #[error("Index is not an int?????")]
ParseFailure(#[from] ParseIntError), ParseFailure(#[from] ParseIntError),
#[error("Not a Newgrounds song")] #[error("Not a Newgrounds song")]
NotNewgrounds, NotNewgrounds,
} }
impl From<reqwest::Error> for SongRequestError {
fn from(_: reqwest::Error) -> Self {Self::ConnectionFailure}
}
impl Song { impl Song {
pub fn get_response(&self) -> Result<SongResponse, SongRequestError> { pub fn get_response(&self) -> Result<SongResponse, SongRequestError> {
match self { match self {
@ -60,7 +65,7 @@ impl Song {
.split("~|~") .split("~|~")
.array_chunks() .array_chunks()
.try_for_each(|[id, value]| -> Result<(), SongRequestError> { .try_for_each(|[id, value]| -> Result<(), SongRequestError> {
out.0[id.parse::<usize>()?] = Some(value.into()); out.0[id.parse::<usize>()? - 1] = Some(value.into());
Ok(()) Ok(())
}) })
.map(|_| out) .map(|_| out)
@ -70,6 +75,36 @@ impl Song {
} }
} }
impl SongResponse {
pub fn id(&self) -> Option<i32> {
self.0[0].and_then(|s| s.parse().ok())
}
pub fn name(&self) -> Option<String> {
self.0[1]
}
pub fn artist_id(&self) -> Option<i32> {
self.0[2].and_then(|s| s.parse().ok())
}
pub fn artist_name(&self) -> Option<String> {
self.0[3]
}
pub fn size(&self) -> Option<i32> {
self.0[4].and_then(|s| s.parse().ok())
}
pub fn video_id(&self) -> Option<String> {
self.0[5]
}
pub fn youtube_url(&self) -> Option<String> {
self.0[6]
}
pub fn song_priority(&self) -> Option<i32> {
self.0[8].and_then(|s| s.parse().ok())
}
pub fn download_link(&self) -> Option<String> {
self.0[9].and_then(|url| urlencoding::decode(&url).ok().map(|url| url.into_owned()))
}
}
impl OuterLevel { impl OuterLevel {
pub fn load_all() -> Vec<OuterLevel> { pub fn load_all() -> Vec<OuterLevel> {
let plist = get_local_level_plist(); let plist = get_local_level_plist();

@ -8,7 +8,9 @@ use eframe::egui;
use std::boxed::Box; use std::boxed::Box;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::fs::File; use std::fs::File;
use std::io::Write;
use thiserror::Error; use thiserror::Error;
use reqwest::blocking as req;
struct PipeDash { struct PipeDash {
msg_queue: VecDeque<Message>, msg_queue: VecDeque<Message>,
@ -59,12 +61,16 @@ struct Song {
enum SongError { enum SongError {
#[error("Not a Newgrounds song")] #[error("Not a Newgrounds song")]
NotNewgrounds, NotNewgrounds,
#[error("Song mp3 not downloaded")] #[error("Song mp3 couldn't be downloaded")]
MissingFile(#[from] std::io::Error), MissingFile(#[from] std::io::Error),
#[error("Couldn't decode mp3 file")] #[error("Couldn't decode mp3 file")]
BrokenSong(#[from] rodio::decoder::DecoderError), BrokenSong(#[from] rodio::decoder::DecoderError),
#[error("Couldn't access song data on servers")] #[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> { struct BeatRateWidget<'a> {
@ -105,19 +111,31 @@ impl Song {
pub fn try_new(gd_song: gd::Song) -> Result<Self, SongError> { pub fn try_new(gd_song: gd::Song) -> Result<Self, SongError> {
match &gd_song { match &gd_song {
gd::Song::Newgrounds { id } => { gd::Song::Newgrounds { id } => {
let song_data = gd_song.get_response()?; let song_response = gd_song.get_response();
file::open(gd::gd_path().join(format!("{}.mp3", id))) let song_path = gd::gd_path().join(format!("{}.mp3", id));
.or_else(|_| { File::open(&song_path)
.or_else(|_| song_response
.clone()
.map_err(|e| e.into())
.and_then(|response: gd::SongResponse| -> Result<File, SongError> {
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)
}) })
todo!("if file is missing, try to download it from the SongResponse. If that fails, return an error. Otherwise, return file info") )
// Ok(Song { .and_then(|file| {
// name: todo!(), let name = song_response
// id, .ok()
// decoder: rodio::Decoder::new_mp3(file)? .and_then(|response| response.name())
// }) .unwrap_or("".into());
}, let decoder = rodio::Decoder::new_mp3(file)?;
_ => Err(SongError::NotNewgrounds) Ok(Song {name, id: *id, decoder})
})
}
_ => Err(SongError::NotNewgrounds),
} }
} }
} }

Loading…
Cancel
Save