diff --git a/.gitignore b/.gitignore index 30cb2d2..cb91d78 100644 --- a/.gitignore +++ b/.gitignore @@ -171,4 +171,6 @@ docs/_book # TODO: where does this rule come from? test/ -static/ \ No newline at end of file +static/ + +bun.lockb \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..e8663b8 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,45 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug executable 'medusa'", + "cargo": { + "args": [ + "build", + "--bin=medusa", + "--package=medusa" + ], + "filter": { + "name": "medusa", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in executable 'medusa'", + "cargo": { + "args": [ + "test", + "--no-run", + "--bin=medusa", + "--package=medusa" + ], + "filter": { + "name": "medusa", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/src/handlers/boot/get_services_handler.rs b/src/handlers/boot/get_services_handler.rs index a16a784..0f180b8 100644 --- a/src/handlers/boot/get_services_handler.rs +++ b/src/handlers/boot/get_services_handler.rs @@ -189,7 +189,7 @@ impl Handler for GetServicesHandler { } async fn handle(&self, model: String, _body: String) -> String { - let common_url = "http://127.0.0.1:8000"; + let common_url = "http://127.0.0.1:8000/ea"; let listening_address = "http://127.0.0.1:8000"; let services = GetServicesHandler::create_core_services_element( diff --git a/src/handlers/common/alive_pcb_tracker_handler.rs b/src/handlers/common/alive_pcb_tracker_handler.rs index cc47116..8c31cd3 100644 --- a/src/handlers/common/alive_pcb_tracker_handler.rs +++ b/src/handlers/common/alive_pcb_tracker_handler.rs @@ -47,8 +47,8 @@ impl Handler for AlivePcbTrackerHandler { .attr("time", &utc_now); writer.write(pcb_tracker).unwrap(); - writer.write(XmlEvent::end_element()).unwrap(); + writer.write(XmlEvent::end_element()).unwrap(); String::from_utf8(buffer.into_inner()).unwrap() diff --git a/src/handlers/common/get_messages_handler.rs b/src/handlers/common/get_messages_handler.rs new file mode 100644 index 0000000..f2ef529 --- /dev/null +++ b/src/handlers/common/get_messages_handler.rs @@ -0,0 +1,48 @@ +use std::io::Cursor; +use crate::handlers::handler::Handler; +use xml::writer::{EmitterConfig, XmlEvent}; + +pub struct GetMessageHandler { + module: String, + method: String, +} + +impl GetMessageHandler { + pub fn new() -> Self { + GetMessageHandler { + module: "message".to_string(), + method: "get".to_string(), + } + } +} + +#[async_trait] +impl Handler for GetMessageHandler { + fn module(&self) -> &str { + &self.module + } + + fn method(&self) -> &str { + &self.method + } + + async fn handle(&self, _model: String, _body: String) -> String { + let mut buffer = Cursor::new(vec![]); + let mut writer = EmitterConfig::new() + .perform_indent(true) + .create_writer(&mut buffer); + + writer.write(XmlEvent::start_element("response")).unwrap(); + + let message = XmlEvent::start_element("message") + .attr("expire", "300") + .attr("status", "0"); + + writer.write(message).unwrap(); + writer.write(XmlEvent::end_element()).unwrap(); + + writer.write(XmlEvent::end_element()).unwrap(); + + String::from_utf8(buffer.into_inner()).unwrap() + } +} diff --git a/src/handlers/common/mod.rs b/src/handlers/common/mod.rs index dd43bc2..1681a77 100644 --- a/src/handlers/common/mod.rs +++ b/src/handlers/common/mod.rs @@ -1,2 +1,6 @@ -pub mod alive_pcb_tracker_handler; \ No newline at end of file +pub mod alive_pcb_tracker_handler; +pub mod get_messages_handler; +pub mod put_pcb_event_handler; + +pub mod ota; \ No newline at end of file diff --git a/src/handlers/common/ota/list_package_handler.rs b/src/handlers/common/ota/list_package_handler.rs new file mode 100644 index 0000000..84ed4d1 --- /dev/null +++ b/src/handlers/common/ota/list_package_handler.rs @@ -0,0 +1,48 @@ +use std::io::Cursor; +use crate::handlers::handler::Handler; +use xml::writer::{EmitterConfig, XmlEvent}; + +pub struct ListPackageHandler { + module: String, + method: String, +} + +impl ListPackageHandler { + pub fn new() -> Self { + ListPackageHandler { + module: "package".to_string(), + method: "list".to_string(), + } + } +} + +#[async_trait] +impl Handler for ListPackageHandler { + fn module(&self) -> &str { + &self.module + } + + fn method(&self) -> &str { + &self.method + } + + async fn handle(&self, _model: String, _body: String) -> String { + let mut buffer = Cursor::new(vec![]); + let mut writer = EmitterConfig::new() + .perform_indent(true) + .create_writer(&mut buffer); + + writer.write(XmlEvent::start_element("response")).unwrap(); + + let message = XmlEvent::start_element("package") + .attr("expire", "600") + .attr("status", "0"); + + writer.write(message).unwrap(); + writer.write(XmlEvent::end_element()).unwrap(); + + writer.write(XmlEvent::end_element()).unwrap(); + + String::from_utf8(buffer.into_inner()).unwrap() + } +} diff --git a/src/handlers/common/ota/mod.rs b/src/handlers/common/ota/mod.rs new file mode 100644 index 0000000..ff7b27a --- /dev/null +++ b/src/handlers/common/ota/mod.rs @@ -0,0 +1,2 @@ +pub mod list_package_handler; +pub mod progress_dl_status_handler; \ No newline at end of file diff --git a/src/handlers/common/ota/progress_dl_status_handler.rs b/src/handlers/common/ota/progress_dl_status_handler.rs new file mode 100644 index 0000000..2464fda --- /dev/null +++ b/src/handlers/common/ota/progress_dl_status_handler.rs @@ -0,0 +1,47 @@ +use std::io::Cursor; +use crate::handlers::handler::Handler; +use xml::writer::{EmitterConfig, XmlEvent}; + +pub struct ProgressDLStatusHandler { + module: String, + method: String, +} + +impl ProgressDLStatusHandler { + pub fn new() -> Self { + ProgressDLStatusHandler { + module: "dlstatus".to_string(), + method: "progress".to_string(), + } + } +} + +#[async_trait] +impl Handler for ProgressDLStatusHandler { + fn module(&self) -> &str { + &self.module + } + + fn method(&self) -> &str { + &self.method + } + + async fn handle(&self, _model: String, _body: String) -> String { + let mut buffer = Cursor::new(vec![]); + let mut writer = EmitterConfig::new() + .perform_indent(true) + .create_writer(&mut buffer); + + writer.write(XmlEvent::start_element("response")).unwrap(); + + let message = XmlEvent::start_element("dlstatus") + .attr("status", "0"); + + writer.write(message).unwrap(); + writer.write(XmlEvent::end_element()).unwrap(); + + writer.write(XmlEvent::end_element()).unwrap(); + + String::from_utf8(buffer.into_inner()).unwrap() + } +} diff --git a/src/handlers/common/put_pcb_event_handler.rs b/src/handlers/common/put_pcb_event_handler.rs new file mode 100644 index 0000000..abd52d5 --- /dev/null +++ b/src/handlers/common/put_pcb_event_handler.rs @@ -0,0 +1,142 @@ +use std::io::{BufReader, Cursor}; +use chrono::DateTime; +use xml::{reader::{EventReader, XmlEvent}, EmitterConfig}; + +use crate::handlers::handler::Handler; + + + +struct PcbEvent { + tag: String, + src_id: String, + model: String, + name: String, + time: String, +} + +pub struct PutPcbEventHandler { + module: String, + method: String, +} + +impl PutPcbEventHandler { + pub fn new() -> Self { + PutPcbEventHandler { + module: "pcbevent".to_string(), + method: "put".to_string(), + } + } + + fn unix_timestamp_to_datetime(timestamp: f64) -> String { + let naive = DateTime::from_timestamp(timestamp as i64, 0); + match naive { + Some(time) => time.format("%Y-%m-%d %H:%M:%S").to_string(), + None => "".to_string(), + } + } + + fn parse_pcb_event(xml_data: &str) -> Option { + let reader = EventReader::new(BufReader::new(xml_data.as_bytes())); + let mut pcb_event = PcbEvent { + tag: String::new(), + src_id: String::new(), + model: String::new(), + name: String::new(), + time: String::new(), + }; + + let mut in_pcbevent = false; + let mut in_item = false; + let mut current_tag = String::new(); + + for event in reader { + match event { + Ok(XmlEvent::StartElement { + name, attributes, .. + }) => { + if name.local_name == "pcbevent" { + in_pcbevent = true; + for attr in attributes { + match attr.name.local_name.as_str() { + "tag" => pcb_event.tag = attr.value.clone(), + "srcid" => pcb_event.src_id = attr.value.clone(), + "model" => pcb_event.model = attr.value.clone(), + _ => {} + } + } + } else if in_pcbevent && name.local_name == "item" { + in_item = true; + } + current_tag = name.local_name.to_string(); + } + Ok(XmlEvent::Characters(content)) => { + if in_item { + match current_tag.as_str() { + "name" => pcb_event.name = content.to_string(), + "time" => { + if let Ok(timestamp) = content.parse::() { + pcb_event.time = Self::unix_timestamp_to_datetime(timestamp); + } + } + _ => {} + } + } + } + Ok(XmlEvent::EndElement { name }) => { + if name.local_name == "item" { + in_item = false; + } else if name.local_name == "pcbevent" { + in_pcbevent = false; + } + } + Err(e) => { + println!("Error: {}", e); + return None; + } + _ => {} + } + } + + Some(pcb_event) + } +} + +#[async_trait] +impl Handler for PutPcbEventHandler { + fn module(&self) -> &str { + &self.module + } + + fn method(&self) -> &str { + &self.method + } + + async fn handle(&self, _model: String, body: String) -> String { + let pcb_event_read = Self::parse_pcb_event(body.as_str()).unwrap(); + + println!("{}", format!( + "PcbEvent {{ tag: '{}', src_id: '{}', model: '{}', name: '{}', time: '{}' }}", + pcb_event_read.tag, + pcb_event_read.src_id, + pcb_event_read.model, + pcb_event_read.name, + pcb_event_read.time + )); + + let mut buffer = Cursor::new(vec![]); + let mut writer = EmitterConfig::new() + .perform_indent(true) + .create_writer(&mut buffer); + + writer.write(xml::writer::XmlEvent::start_element("response")).unwrap(); + + let pcb_event = xml::writer::XmlEvent::start_element("pcbevent").attr("status", "0"); + + writer.write(pcb_event).unwrap(); + writer.write(xml::writer::XmlEvent::end_element()).unwrap(); + + writer.write(xml::writer::XmlEvent::end_element()).unwrap(); + + String::from_utf8(buffer.into_inner()).unwrap() + } +} diff --git a/src/handlers/core_e_amuse_request_handler.rs b/src/handlers/core_e_amuse_request_handler.rs index 7dbdf84..166a1e8 100644 --- a/src/handlers/core_e_amuse_request_handler.rs +++ b/src/handlers/core_e_amuse_request_handler.rs @@ -77,13 +77,21 @@ pub async fn handle_e_amuse_request(model: &str, module: &str, method: &str, dat let mut response_xml = String::new(); + let mut handler_found = false; + for handler in handlers.iter() { if handler.module() == module && handler.method() == method { let model = model.to_string(); let xml_string = xml_string.clone(); response_xml = handler.handle(model, xml_string).await; + handler_found = true; } } + + if !handler_found { + eprintln!("No handler found for module: {} and method: {}", module, method); + return Err(BadRequest("No handler found".to_string())); + } let response = ResponseGenerator::generate_response(response_xml.into(), is_compressed, info.clone()); // Return the binary data wrapped in BinaryResponse diff --git a/src/handlers/handler.rs b/src/handlers/handler.rs index 64b138d..4f68d04 100644 --- a/src/handlers/handler.rs +++ b/src/handlers/handler.rs @@ -1,4 +1,3 @@ -use std::{future::Future, sync::Arc}; #[async_trait] pub trait Handler: Send + Sync { diff --git a/src/main.rs b/src/main.rs index 17c09aa..366fb7d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,11 +9,15 @@ mod routes; use handlers::boot::get_services_handler::GetServicesHandler; use handlers::common::alive_pcb_tracker_handler::AlivePcbTrackerHandler; +use handlers::common::get_messages_handler::GetMessageHandler; +use handlers::common::ota::list_package_handler::ListPackageHandler; +use handlers::common::ota::progress_dl_status_handler::ProgressDLStatusHandler; +use handlers::common::put_pcb_event_handler::PutPcbEventHandler; use handlers::handler::Handler; use rocket::fs::{relative, FileServer, NamedFile}; -use routes::ea::core_route::handle_nostalgia_request; use routes::ea::core_route::handle_query_request; use routes::ea::core_route::handle_slash_request; +use routes::ea::e_amusement_route::handle_ea_query_request; #[macro_use] extern crate lazy_static; #[macro_use] extern crate rocket; @@ -24,12 +28,22 @@ async fn index() -> Option { } fn register_handlers() -> Vec> { - let mut handlers: Vec> = Vec::new(); let get_services_handler = Arc::new(GetServicesHandler::new()); let alive_pcb_tracker_handler = Arc::new(AlivePcbTrackerHandler::new()); - handlers.push(get_services_handler); - handlers.push(alive_pcb_tracker_handler); + let get_messages_handler = Arc::new(GetMessageHandler::new()); + let put_pcb_event_handler = Arc::new(PutPcbEventHandler::new()); + let list_package_handler = Arc::new(ListPackageHandler::new()); + let progress_dl_status_handler = Arc::new(ProgressDLStatusHandler::new()); + + let handlers: Vec> = vec![ + get_services_handler, + alive_pcb_tracker_handler, + get_messages_handler, + put_pcb_event_handler, + list_package_handler, + progress_dl_status_handler + ]; handlers } @@ -40,6 +54,6 @@ fn rocket() -> _ { rocket::build() .manage(handlers) - .mount("/", routes![index, handle_query_request, handle_slash_request, handle_slash_request, handle_nostalgia_request]) + .mount("/", routes![index, handle_query_request, handle_slash_request, handle_ea_query_request]) .mount("/", FileServer::from(relative!("static"))) } diff --git a/src/parsers/header_parser.rs b/src/parsers/header_parser.rs index 52ffb50..0f916d0 100644 --- a/src/parsers/header_parser.rs +++ b/src/parsers/header_parser.rs @@ -33,7 +33,7 @@ impl<'r> FromRequest<'r> for EAMuseHeaders { let is_compressed = matches!(is_compressed_header, Some(value) if value == "true"); // Get the `info` header, or set a default if it's not found - let info = info_header.unwrap_or("default_info").to_string(); + let info = info_header.unwrap_or("").to_string(); // Construct the EAMuseHeaders struct let eamuse_headers = EAMuseHeaders { diff --git a/src/routes/ea/core_route.rs b/src/routes/ea/core_route.rs index 50d2295..1ee23f7 100644 --- a/src/routes/ea/core_route.rs +++ b/src/routes/ea/core_route.rs @@ -14,25 +14,19 @@ e_amuse_headers: EAMuseHeaders, handlers: &State>>) -> Resu handle_e_amuse_request(model, module, method, data, is_compressed, info, handlers.to_vec()).await } -#[post("/?&&", data = "")] -pub async fn handle_query_request(model: &str, module: &str, method: &str, data: Data<'_>, +#[post("/?&&&", data = "")] +pub async fn handle_query_request(model: &str, f: Option, module: Option, method: Option, data: Data<'_>, e_amuse_headers: EAMuseHeaders, handlers: &State>>) -> Result> { let is_compressed = e_amuse_headers.is_compressed; let info = e_amuse_headers.info; - handle_e_amuse_request(model, module, method, data, is_compressed, info, handlers.to_vec()).await + if f.is_some() { + let f = f.unwrap(); + let f_split = f.split('.').collect::>(); + let module = Some(f_split[0].to_string()); + let method = Some(f_split[1].to_string()); + return handle_e_amuse_request(model, module.unwrap().as_str(), method.unwrap().as_str(), data, is_compressed, info, handlers.to_vec()).await; + } + + handle_e_amuse_request(model, module.unwrap().as_str(), method.unwrap().as_str(), data, is_compressed, info, handlers.to_vec()).await } - -// TODO weird nostalgia request url -#[post("/?", data = "")] -pub async fn handle_nostalgia_request(model: &str, f: &str, data: Data<'_>, -e_amuse_headers: EAMuseHeaders, handlers: &State>>) -> Result> { - let is_compressed = e_amuse_headers.is_compressed; - let info = e_amuse_headers.info; - - let f_splt = f.split('.').collect::>(); - let module = f_splt[0]; - let service = f_splt[1]; - - handle_e_amuse_request(model, module, service, data, is_compressed, info, handlers.to_vec()).await -} \ No newline at end of file diff --git a/src/routes/ea/e_amusement_route.rs b/src/routes/ea/e_amusement_route.rs new file mode 100644 index 0000000..b05f51a --- /dev/null +++ b/src/routes/ea/e_amusement_route.rs @@ -0,0 +1,23 @@ +use std::sync::Arc; +use rocket::response::status::BadRequest; +use rocket::data::Data; +use rocket::State; + +use crate::{handlers::{core_e_amuse_request_handler::{handle_e_amuse_request, BinaryResponse}, handler::Handler}, parsers::header_parser::EAMuseHeaders}; + +#[post("/ea/<_module>?&&&", data = "")] +pub async fn handle_ea_query_request(_module: &str, model: &str, f: Option, module: Option, method: Option, data: Data<'_>, +e_amuse_headers: EAMuseHeaders, handlers: &State>>) -> Result> { + let is_compressed = e_amuse_headers.is_compressed; + let info = e_amuse_headers.info; + + if f.is_some() { + let f = f.unwrap(); + let f_split = f.split('.').collect::>(); + let module = Some(f_split[0].to_string()); + let method = Some(f_split[1].to_string()); + return handle_e_amuse_request(model, module.unwrap().as_str(), method.unwrap().as_str(), data, is_compressed, info, handlers.to_vec()).await; + } + + handle_e_amuse_request(model, module.unwrap().as_str(), method.unwrap().as_str(), data, is_compressed, info, handlers.to_vec()).await +} \ No newline at end of file diff --git a/src/routes/ea/mod.rs b/src/routes/ea/mod.rs index 8f4ef2c..bd3c7cb 100644 --- a/src/routes/ea/mod.rs +++ b/src/routes/ea/mod.rs @@ -1,2 +1,3 @@ -pub mod core_route; \ No newline at end of file +pub mod core_route; +pub mod e_amusement_route; \ No newline at end of file diff --git a/web/bun.lockb b/web/bun.lockb index c66bc68..13530a0 100644 Binary files a/web/bun.lockb and b/web/bun.lockb differ diff --git a/web/package.json b/web/package.json index 2ac35e4..17c09c8 100644 --- a/web/package.json +++ b/web/package.json @@ -25,10 +25,14 @@ "@vue/eslint-config-prettier": "^9.0.0", "@vue/eslint-config-typescript": "^13.0.0", "@vue/tsconfig": "^0.5.1", + "autoprefixer": "^10.4.20", + "daisyui": "^4.12.10", "eslint": "^8.57.0", "eslint-plugin-vue": "^9.23.0", "npm-run-all2": "^6.2.0", + "postcss": "^8.4.45", "prettier": "^3.2.5", + "tailwindcss": "^3.4.10", "typescript": "~5.4.0", "vite": "^5.3.1", "vite-plugin-vue-devtools": "^7.3.1", diff --git a/web/postcss.config.js b/web/postcss.config.js new file mode 100644 index 0000000..2e7af2b --- /dev/null +++ b/web/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/web/src/assets/base.css b/web/src/assets/base.css deleted file mode 100644 index 8816868..0000000 --- a/web/src/assets/base.css +++ /dev/null @@ -1,86 +0,0 @@ -/* color palette from */ -:root { - --vt-c-white: #ffffff; - --vt-c-white-soft: #f8f8f8; - --vt-c-white-mute: #f2f2f2; - - --vt-c-black: #181818; - --vt-c-black-soft: #222222; - --vt-c-black-mute: #282828; - - --vt-c-indigo: #2c3e50; - - --vt-c-divider-light-1: rgba(60, 60, 60, 0.29); - --vt-c-divider-light-2: rgba(60, 60, 60, 0.12); - --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65); - --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48); - - --vt-c-text-light-1: var(--vt-c-indigo); - --vt-c-text-light-2: rgba(60, 60, 60, 0.66); - --vt-c-text-dark-1: var(--vt-c-white); - --vt-c-text-dark-2: rgba(235, 235, 235, 0.64); -} - -/* semantic color variables for this project */ -:root { - --color-background: var(--vt-c-white); - --color-background-soft: var(--vt-c-white-soft); - --color-background-mute: var(--vt-c-white-mute); - - --color-border: var(--vt-c-divider-light-2); - --color-border-hover: var(--vt-c-divider-light-1); - - --color-heading: var(--vt-c-text-light-1); - --color-text: var(--vt-c-text-light-1); - - --section-gap: 160px; -} - -@media (prefers-color-scheme: dark) { - :root { - --color-background: var(--vt-c-black); - --color-background-soft: var(--vt-c-black-soft); - --color-background-mute: var(--vt-c-black-mute); - - --color-border: var(--vt-c-divider-dark-2); - --color-border-hover: var(--vt-c-divider-dark-1); - - --color-heading: var(--vt-c-text-dark-1); - --color-text: var(--vt-c-text-dark-2); - } -} - -*, -*::before, -*::after { - box-sizing: border-box; - margin: 0; - font-weight: normal; -} - -body { - min-height: 100vh; - color: var(--color-text); - background: var(--color-background); - transition: - color 0.5s, - background-color 0.5s; - line-height: 1.6; - font-family: - Inter, - -apple-system, - BlinkMacSystemFont, - 'Segoe UI', - Roboto, - Oxygen, - Ubuntu, - Cantarell, - 'Fira Sans', - 'Droid Sans', - 'Helvetica Neue', - sans-serif; - font-size: 15px; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} diff --git a/web/src/assets/main.css b/web/src/assets/main.css index 36fb845..bd6213e 100644 --- a/web/src/assets/main.css +++ b/web/src/assets/main.css @@ -1,35 +1,3 @@ -@import './base.css'; - -#app { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - font-weight: normal; -} - -a, -.green { - text-decoration: none; - color: hsla(160, 100%, 37%, 1); - transition: 0.4s; - padding: 3px; -} - -@media (hover: hover) { - a:hover { - background-color: hsla(160, 100%, 37%, 0.2); - } -} - -@media (min-width: 1024px) { - body { - display: flex; - place-items: center; - } - - #app { - display: grid; - grid-template-columns: 1fr 1fr; - padding: 0 2rem; - } -} +@tailwind base; +@tailwind components; +@tailwind utilities; \ No newline at end of file diff --git a/web/src/components/HelloWorld.vue b/web/src/components/HelloWorld.vue index 38d821e..b6a59ab 100644 --- a/web/src/components/HelloWorld.vue +++ b/web/src/components/HelloWorld.vue @@ -6,7 +6,7 @@ defineProps<{