diff --git a/Cargo.lock b/Cargo.lock index 7b153ba..457b7d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -978,13 +978,13 @@ name = "open_tavern" version = "0.1.0" dependencies = [ "axum", - "base64", "futures-util", "serde", "serde_json", "sqlx", "tavern_macros", "tokio", + "tokio-util", ] [[package]] @@ -1751,6 +1751,19 @@ dependencies = [ "tungstenite", ] +[[package]] +name = "tokio-util" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "tower" version = "0.4.13" diff --git a/Cargo.toml b/Cargo.toml index d6b1b0c..138d9fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ axum = { version = "0.8.4", features = ["ws"] } serde = "1.*.*" serde_json = "1.*.*" tokio = { version = "1.*.*", features = ["full"] } +tokio-util = { version = "0.7.15", features = ["io"] } sqlx = { version = "0.8.6", features = ["runtime-tokio", "sqlite"] } tavern_macros = { version = "0.1.0", path = "tavern_macros" } futures-util = "0.3.31" -base64 = "0.22.1" diff --git a/assets/web/app.js b/assets/web/app.js index b583ac8..12c13a7 100644 --- a/assets/web/app.js +++ b/assets/web/app.js @@ -145,7 +145,7 @@ tavern.onspawntoken = (t) => { token.style.left = `${t.x * GRID_SIZE}px`; token.token_id = t.token_id; token.innerHTML = ` - + ` token.onmousedown = (e) => { token.classList.remove('token-transition'); diff --git a/src/lib.rs b/src/lib.rs index c5ddc1a..42709c3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -144,15 +144,13 @@ impl GameServer { api::Request::GetTokens { scene } => { for token_id in self.game.available_tokens(scene) { if let Some(ti) = self.game.token_info(0, token_id) { - let bits = std::fs::read(&ti.img_source).expect("FAILED READING TOKEN IMAGE"); - let img = base64::Engine::encode(&base64::prelude::BASE64_STANDARD, &bits); _ = broadcast.send(( Some(id.clone()), api::Response::SpawnToken(SpawnToken { token_id: token_id, x: ti.x, y: ti.y, - img, + img: ti.img_source.clone(), }), )); } @@ -160,7 +158,7 @@ impl GameServer { } api::Request::GetCurrentScene => { let scene = self.game.current_scene(); - let mut scene_tokens = self + let scene_tokens = self .game .available_tokens(scene) .iter() @@ -173,10 +171,6 @@ impl GameServer { img: info.img_source.clone(), }) .collect::>(); - for spawn in scene_tokens.iter_mut() { - let bits = std::fs::read(&spawn.img).expect("FAILED READING TOKEN IMAGE"); - spawn.img = base64::Engine::encode(&base64::prelude::BASE64_STANDARD, &bits); - } _ = broadcast.send(( Some(id.clone()), api::Response::ShowScene { @@ -193,11 +187,9 @@ impl GameServer { img_path, } => { let token_id = self.game.create_token(map_id, character, img_path.clone(), x, y); - let bits = std::fs::read(&img_path).expect("FAILED READING TOKEN IMAGE"); - let img = base64::Engine::encode(&base64::prelude::BASE64_STANDARD, &bits); _ = broadcast.send(( Some(id.clone()), - api::Response::SpawnToken(SpawnToken { token_id, x, y, img }), + api::Response::SpawnToken(SpawnToken { token_id, x, y, img: img_path.clone() }), )); } api::Request::MoveToken { token_id, x, y } => { diff --git a/src/main.rs b/src/main.rs index 18d5794..6f26a18 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,5 @@ use axum::{ - Router, - extract::ws::{self, Message}, - response, routing, + extract::ws::{self, Message}, http::StatusCode, response::{self, IntoResponse}, routing, Router }; use futures_util::{ SinkExt, StreamExt, @@ -9,6 +7,7 @@ use futures_util::{ }; use open_tavern::api::{Request, RequestError, Response}; use tokio::sync::{broadcast, mpsc}; +use tokio_util::io::ReaderStream; #[tokio::main] async fn main() { @@ -20,6 +19,7 @@ async fn main() { .route("/tavern.js", routing::get(socket)) .route("/app.js", routing::get(app_js)) .route("/style.css", routing::get(style)) + .route("/assets/{*asset}", routing::get(get_asset)) .route( "/ws", routing::get(move |w| ws_handler(w, msend, bsend2.clone().subscribe())), @@ -191,3 +191,28 @@ async fn style() -> impl response::IntoResponse { std::fs::read_to_string("./assets/web/style.css").unwrap(), ) } + +async fn get_asset(asset: axum::extract::Path) -> impl IntoResponse { + println!("Asset requested: {}", asset.0); + let supported_file_types = [ + (".jpg", "image/jpeg"), (".jpeg", "image/jpeg"), (".png", "image/png") + ]; + let mime = match supported_file_types.iter() + .filter(|t| asset.0.ends_with(t.0)) + .map(|t| t.1) + .next() { + Some(t) => t, + None => return Err((StatusCode::UNSUPPORTED_MEDIA_TYPE, "Unsupported file type".to_string())), + }; + let file = match tokio::fs::File::open(format!("assets/{}", asset.0)).await { + Ok(f) => f, + Err(err) => return Err((StatusCode::NOT_FOUND, format!("File not found: {}", err))), + }; + let stream = ReaderStream::new(file); + let body = axum::body::Body::from_stream(stream); + + let headers = [ + (axum::http::header::CONTENT_TYPE, mime) + ]; + Ok((headers, body)) +} \ No newline at end of file