diff --git a/bins/Cargo.lock b/bins/Cargo.lock index d1ae8ee7..80a4cee7 100644 --- a/bins/Cargo.lock +++ b/bins/Cargo.lock @@ -119,6 +119,7 @@ dependencies = [ "bitflags", "bytes", "futures-util", + "headers", "http", "http-body", "hyper", @@ -187,7 +188,6 @@ dependencies = [ "mime_guess", "serde", "serde_json", - "stream-future", "tauri", "tauri-build", "tauri-plugin-window-state", @@ -1496,6 +1496,31 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "headers" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" +dependencies = [ + "base64 0.13.1", + "bitflags", + "bytes", + "headers-core", + "http", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http", +] + [[package]] name = "heck" version = "0.3.3" @@ -3071,6 +3096,17 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "sha1" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sha2" version = "0.10.6" diff --git a/bins/ayaka-gui/src-tauri/Cargo.toml b/bins/ayaka-gui/src-tauri/Cargo.toml index 61678863..6295d471 100644 --- a/bins/ayaka-gui/src-tauri/Cargo.toml +++ b/bins/ayaka-gui/src-tauri/Cargo.toml @@ -16,10 +16,9 @@ serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } tauri = { version = "1.2", features = ["cli", "window-all"] } tauri-plugin-window-state = "0.1" -axum = { version = "0.6", default-features = false, features = ["http1", "tokio"] } +axum = { version = "0.6", default-features = false, features = ["http1", "tokio", "headers"] } tower-http = { version = "0.4", features = ["cors", "trace"] } mime_guess = "2.0" -stream-future = "0.3" [features] default = [ "custom-protocol" ] diff --git a/bins/ayaka-gui/src-tauri/src/asset_resolver.rs b/bins/ayaka-gui/src-tauri/src/asset_resolver.rs index 87ce7f56..6ca7000c 100644 --- a/bins/ayaka-gui/src-tauri/src/asset_resolver.rs +++ b/bins/ayaka-gui/src-tauri/src/asset_resolver.rs @@ -1,7 +1,8 @@ use axum::{ - body::{Body, Bytes, StreamBody}, - extract::Path, - http::{header::CONTENT_TYPE, Request, StatusCode}, + body::Body, + extract::{Path, TypedHeader}, + headers::{ContentRange, ContentType, HeaderMapExt, Range}, + http::{header::CONTENT_TYPE, HeaderMap, Request, StatusCode}, response::{IntoResponse, Response}, routing::get, Router, Server, @@ -9,11 +10,11 @@ use axum::{ use ayaka_model::vfs::{error::VfsErrorKind, *}; use std::{ fmt::Display, - io::{BorrowedBuf, Read}, + io::{BorrowedBuf, Read, SeekFrom}, net::TcpListener, + ops::Bound, sync::OnceLock, }; -use stream_future::try_stream; use tauri::{ plugin::{Builder, TauriPlugin}, AppHandle, Runtime, @@ -63,47 +64,76 @@ impl From for ResolverError { } } -const BUFFER_LEN: usize = 1048576; +struct RangeNotSatisfiableError; -fn read_buf_vec(mut file: impl Read, vec: &mut Vec) -> std::io::Result { - let old_len = vec.len(); - let mut read_buf = BorrowedBuf::from(vec.spare_capacity_mut()); - let mut cursor = read_buf.unfilled(); - file.read_buf(cursor.reborrow())?; - let written = cursor.written(); - unsafe { - vec.set_len(old_len + written); +impl From for ResolverError { + fn from(_: RangeNotSatisfiableError) -> Self { + Self(StatusCode::RANGE_NOT_SATISFIABLE, String::new()) } - Ok(written) } -#[try_stream(Bytes)] -fn file_stream(mut file: Box, length: usize) -> std::io::Result<()> { - let length = length.min(BUFFER_LEN); - loop { - let mut buffer = Vec::with_capacity(length); - let read_bytes = read_buf_vec(&mut file, &mut buffer)?; - if read_bytes > 0 { - yield Bytes::from(buffer); - } else { - break; - } +fn get_first_range(range: Range, length: u64) -> Option<(u64, u64)> { + let mut iter = range.iter(); + let (start, end) = iter.next()?; + // We don't support multiple ranges. + if let Some(_) = iter.next() { + return None; + } + let start = match start { + Bound::Included(i) => i, + Bound::Excluded(i) => i - 1, + Bound::Unbounded => 0, + }; + let end = match end { + Bound::Included(i) => i + 1, + Bound::Excluded(i) => i, + Bound::Unbounded => length, + }; + if end > length { + None + } else { + Some((start, end)) + } +} + +fn read_buf_exact(mut file: impl Read, buffer: &mut Vec, length: usize) -> std::io::Result<()> { + let old_len = buffer.len(); + buffer.reserve_exact(length); + // SAFETY: reserved + let mut read_buf = + BorrowedBuf::from(unsafe { buffer.spare_capacity_mut().get_unchecked_mut(..length) }); + let cursor = read_buf.unfilled(); + file.read_buf_exact(cursor)?; + // SAFETY: read exact + unsafe { + buffer.set_len(old_len + length); } Ok(()) } -async fn fs_resolver(Path(path): Path) -> Result { +async fn fs_resolver( + Path(path): Path, + range: Option>, +) -> Result { let path = ROOT_PATH.get().expect("cannot get ROOT_PATH").join(path)?; - let file = path.open_file()?; let mime = mime_guess::from_path(path.as_str()).first_or_octet_stream(); - let length = path - .metadata() - .map(|meta| meta.len as usize) - .unwrap_or(BUFFER_LEN); - Ok(( - [(CONTENT_TYPE, mime.to_string())], - StreamBody::new(file_stream(file, length)), - )) + let mut header_map = HeaderMap::new(); + header_map.typed_insert(ContentType::from(mime)); + let mut file = path.open_file()?; + if let Some(TypedHeader(range)) = range { + let length = path.metadata()?.len; + let (start, end) = get_first_range(range, length).ok_or(RangeNotSatisfiableError)?; + let read_length = end - start; + let mut buffer = vec![]; + file.seek(SeekFrom::Start(start))?; + read_buf_exact(file, &mut buffer, read_length as usize)?; + header_map.typed_insert(ContentRange::bytes(start..end, length).unwrap()); + Ok((StatusCode::PARTIAL_CONTENT, header_map, buffer)) + } else { + let mut buffer = vec![]; + file.read_to_end(&mut buffer)?; + Ok((StatusCode::OK, header_map, buffer)) + } } async fn resolver(app: AppHandle, req: Request) -> impl IntoResponse { diff --git a/bins/ayaka-gui/src-tauri/src/main.rs b/bins/ayaka-gui/src-tauri/src/main.rs index 657790df..3972a165 100644 --- a/bins/ayaka-gui/src-tauri/src/main.rs +++ b/bins/ayaka-gui/src-tauri/src/main.rs @@ -2,7 +2,6 @@ all(not(debug_assertions), target_os = "windows"), windows_subsystem = "windows" )] -#![feature(generators)] #![feature(once_cell)] #![feature(read_buf)] #![feature(return_position_impl_trait_in_trait)]