diff --git a/Cargo.lock b/Cargo.lock index 9f58f0c..4258b50 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1517,6 +1517,12 @@ dependencies = [ "libc", ] +[[package]] +name = "md5" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" + [[package]] name = "memchr" version = "2.5.0" @@ -2026,6 +2032,7 @@ dependencies = [ "flate2", "gd_plist", "home", + "md5", "ordered-float", "reqwest", "rodio", diff --git a/Cargo.toml b/Cargo.toml index cbabc06..e8a1004 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,3 +17,4 @@ ordered-float = "3.4.0" thiserror = "1.0.38" reqwest = {version = "0.11.14", features = [ "blocking" ]} urlencoding = "2.1.2" +md5 = "0.7.0" diff --git a/src/gd.rs b/src/gd.rs index 40faf0d..fed47f8 100644 --- a/src/gd.rs +++ b/src/gd.rs @@ -29,25 +29,27 @@ struct Level { song: Song, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct OuterLevel { name: String, // k2 revision: Option, // k46 } -#[derive(Debug, Error, Clone)] +pub struct InnerLevel(String); + +#[derive(Debug, Error)] pub enum SongRequestError { #[error("Request failed")] - ConnectionFailure, + ConnectionFailure(#[from] reqwest::Error), #[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 InnerLevel { + pub fn try_from_encoded_ils(ils: &str) -> Option { + todo!() } } @@ -108,8 +110,7 @@ impl SongResponse { impl OuterLevel { pub fn load_all() -> Vec { - let plist = get_local_level_plist(); - let levels: Vec = plist + get_local_level_plist() .as_dictionary() .and_then(|dict| dict.get("LLM_01")) .unwrap() @@ -128,8 +129,31 @@ impl OuterLevel { } builder.build_outer_level().unwrap() }) - .collect(); - levels + .collect() + } + + pub fn load_inner(&self) -> InnerLevel { + get_local_level_plist() + .as_dictionary() + .and_then(|dict| dict.get("LLM_01")) + .unwrap() + .as_dictionary() + .unwrap() + .iter() + .find(|(key, val)| key.as_str() != "_isArr" && { + let props = val.as_dictionary().unwrap(); + props.get("k2").unwrap().as_string().unwrap() == self.name + && props.get("k46").and_then(|rev| rev.as_signed_integer()) == self.revision + }) + .unwrap() + .1 + .as_dictionary() + .unwrap() + .get("k4") + .unwrap() + .as_string() + .and_then(|str| InnerLevel::try_from_encoded_ils(str)) + .unwrap() } pub fn display_name(&self) -> LayoutJob { diff --git a/src/main.rs b/src/main.rs index 9189fad..20f835a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ #![feature(iter_array_chunks)] -//#![warn(clippy::all, clippy::pedantic, clippy::nursery)] +#![warn(clippy::all, clippy::pedantic, clippy::nursery)] +#![allow(dead_code)] mod gd; mod music; @@ -11,13 +12,17 @@ use std::collections::VecDeque; use std::fs::File; use std::io::Write; use thiserror::Error; +use std::error::Error; +use std::mem; struct PipeDash { msg_queue: VecDeque, selected_level: Option, level_list: Vec, loaded_song: Option, + loaded_level_checksum: Option<(gd::OuterLevel, md5::Digest)>, editor: Editor, + error: Option> } #[derive(Debug, PartialEq, Eq)] @@ -30,6 +35,8 @@ enum Color { #[derive(Debug)] enum Message { LevelSelected(usize), + CloseError, + LoadLevel, } struct Editor { @@ -252,6 +259,8 @@ impl PipeDash { msg_queue: VecDeque::new(), level_list: gd::OuterLevel::load_all(), loaded_song: None, + loaded_level_checksum: None, + error: None, editor: Editor { state: EditorState { scroll_pos: 0f32, @@ -274,7 +283,9 @@ impl PipeDash { .default_width(100f32) .show(ctx, |ui| { ui.with_layout(egui::Layout::top_down(egui::Align::LEFT), |ui| { - ui.button("Load Level"); + if ui.add_enabled(self.selected_level.is_some(), egui::Button::new("Load Level")).clicked() { + self.msg_queue.push_back(Message::LoadLevel); + } egui::ScrollArea::vertical().show(ui, |ui| { ui.with_layout(egui::Layout::top_down_justified(egui::Align::Min), |ui| { for (idx, level) in self.level_list.iter().enumerate() { @@ -324,14 +335,27 @@ impl PipeDash { }); } + fn handle_message(&mut self, message: Message) { + match message { + Message::LevelSelected(idx) => self.selected_level = Some(idx), + Message::CloseError => self.error = None, + Message::LoadLevel => { + // Load song & GdlData + let level = self.selected_level + .and_then(|idx| self.level_list.get(idx)) + .unwrap() // will not panic. selected_level range is same as level_list... + .clone(); // ...length - 1; message will not be sent if selected_level is none + + todo!(); + }, + } + + } + fn handle_messages(&mut self) { - for message in self.msg_queue.drain(..) { + for message in mem::take(&mut self.msg_queue) { println!("{message:?}"); - match message { - Message::LevelSelected(idx) => { - self.selected_level = Some(idx); - } - } + self.handle_message(message); } } } @@ -340,8 +364,17 @@ impl eframe::App for PipeDash { fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { ctx.set_pixels_per_point(2f32); - self.side_panel(ctx, frame); - self.center_panel(ctx, frame); + if let Some(boxed_err) = &self.error { + egui::CentralPanel::default().show(ctx, |ui| { + ui.label(boxed_err.to_string()); + if ui.button("Close").clicked() { + self.msg_queue.push_back(Message::CloseError); + } + }); + } else { + self.side_panel(ctx, frame); + self.center_panel(ctx, frame); + } self.handle_messages(); }