files.rs
1 use alloc::string::String as AllocString; 2 3 use picoserve::response::{IntoResponse, StatusCode}; 4 5 use crate::filesystems::sd::is_supported_flat_file_name; 6 7 const CHUNK_SIZE: usize = 4096; 8 9 fn content_type_for(name: &str) -> &'static str { 10 if name.ends_with(".html") || name.ends_with(".htm") { 11 "text/html; charset=utf-8" 12 } else if name.ends_with(".js") { 13 "application/javascript; charset=utf-8" 14 } else if name.ends_with(".wasm") || name.ends_with(".was") { 15 "application/wasm" 16 } else if name.ends_with(".css") { 17 "text/css" 18 } else { 19 "application/octet-stream" 20 } 21 } 22 23 pub struct IndexService; 24 25 impl picoserve::routing::RequestHandlerService<()> for IndexService { 26 async fn call_request_handler_service< 27 R: picoserve::io::Read, 28 W: picoserve::response::ResponseWriter<Error = R::Error>, 29 >( 30 &self, 31 _state: &(), 32 (): (), 33 request: picoserve::request::Request<'_, R>, 34 response_writer: W, 35 ) -> Result<picoserve::ResponseSent, W::Error> { 36 let size = match crate::filesystems::sd::file_size_at("", "index.htm") { 37 Ok(size) => size, 38 Err(_) => { 39 return (StatusCode::NOT_FOUND, "index.htm not found on SD card\n") 40 .write_to(request.body_connection.finalize().await?, response_writer) 41 .await; 42 } 43 }; 44 45 picoserve::response::chunked::ChunkedResponse::new(SdCardChunks { 46 file_name: AllocString::from("index.htm"), 47 size, 48 content_type: "text/html; charset=utf-8", 49 }) 50 .write_to(request.body_connection.finalize().await?, response_writer) 51 .await 52 } 53 } 54 55 pub struct StaticFileService; 56 57 impl picoserve::routing::RequestHandlerService<(), (AllocString,)> for StaticFileService { 58 async fn call_request_handler_service< 59 R: picoserve::io::Read, 60 W: picoserve::response::ResponseWriter<Error = R::Error>, 61 >( 62 &self, 63 _state: &(), 64 (file_name,): (AllocString,), 65 request: picoserve::request::Request<'_, R>, 66 response_writer: W, 67 ) -> Result<picoserve::ResponseSent, W::Error> { 68 if !is_supported_flat_file_name(&file_name) { 69 return (StatusCode::BAD_REQUEST, "invalid path\n") 70 .write_to(request.body_connection.finalize().await?, response_writer) 71 .await; 72 } 73 74 let size = match crate::filesystems::sd::file_size_at("", &file_name) { 75 Ok(size) => size, 76 Err(_) => { 77 return (StatusCode::NOT_FOUND, "not found\n") 78 .write_to(request.body_connection.finalize().await?, response_writer) 79 .await; 80 } 81 }; 82 83 let content_type = content_type_for(&file_name); 84 85 picoserve::response::chunked::ChunkedResponse::new(SdCardChunks { 86 file_name, 87 size, 88 content_type, 89 }) 90 .write_to(request.body_connection.finalize().await?, response_writer) 91 .await 92 } 93 } 94 95 struct SdCardChunks { 96 file_name: AllocString, 97 size: u32, 98 content_type: &'static str, 99 } 100 101 impl picoserve::response::chunked::Chunks for SdCardChunks { 102 fn content_type(&self) -> &'static str { 103 self.content_type 104 } 105 106 async fn write_chunks<W: picoserve::io::Write>( 107 self, 108 mut writer: picoserve::response::chunked::ChunkWriter<W>, 109 ) -> Result<picoserve::response::chunked::ChunksWritten, W::Error> { 110 let mut offset = 0u32; 111 let mut buffer = [0u8; CHUNK_SIZE]; 112 113 while offset < self.size { 114 let bytes_read = match crate::filesystems::sd::read_file_chunk( 115 &self.file_name, 116 offset, 117 &mut buffer, 118 ) { 119 Ok(count) if count > 0 => count, 120 _ => break, 121 }; 122 writer.write_chunk(&buffer[..bytes_read]).await?; 123 offset += bytes_read as u32; 124 } 125 126 writer.finalize().await 127 } 128 }