stream books instead of reading them into memory

This commit is contained in:
Sebastian Hugentobler 2024-05-10 17:23:23 +02:00
parent d7f056f77e
commit d00a7ef8dc
Signed by: shu
GPG Key ID: BB32CF3CA052C2F0
3 changed files with 18 additions and 11 deletions

1
Cargo.lock generated
View File

@ -1371,6 +1371,7 @@ dependencies = [
"thiserror", "thiserror",
"time", "time",
"tokio", "tokio",
"tokio-util",
"tracing", "tracing",
"tracing-subscriber", "tracing-subscriber",
"uuid", "uuid",

View File

@ -17,6 +17,7 @@ tera = "1.19.1"
thiserror = { workspace = true } thiserror = { workspace = true }
time = { workspace = true } time = { workspace = true }
tokio = { version = "1.37.0", features = ["rt-multi-thread", "macros"] } tokio = { version = "1.37.0", features = ["rt-multi-thread", "macros"] }
tokio-util = "0.7.11"
tracing = "0.1.40" tracing = "0.1.40"
tracing-subscriber = "0.3.18" tracing-subscriber = "0.3.18"
uuid = { version = "1.8.0", features = ["v4", "fast-rng"] } uuid = { version = "1.8.0", features = ["v4", "fast-rng"] }

View File

@ -1,18 +1,22 @@
//! Handle requests for specific formats of a book. //! Handle requests for specific formats of a book.
use std::{fs::File, io::Read, sync::Arc}; use std::sync::Arc;
use tokio::fs::File;
use poem::{ use poem::{
error::NotFoundError, error::NotFoundError,
handler, handler,
web::{Data, Path, WithContentType, WithHeader}, web::{Data, Path},
IntoResponse, Body, IntoResponse, Response,
}; };
use tokio_util::io::ReaderStream;
use crate::{ use crate::{
app_state::AppState, app_state::AppState,
data::book::{Book, Format}, data::book::{Book, Format},
handlers::error::HandlerError, handlers::error::HandlerError,
opds::media_type::MediaType,
}; };
/// Handle a request for a book with id `id` in format `format`. /// Handle a request for a book with id `id` in format `format`.
@ -20,7 +24,7 @@ use crate::{
pub async fn handler( pub async fn handler(
Path((id, format)): Path<(u64, String)>, Path((id, format)): Path<(u64, String)>,
state: Data<&Arc<AppState>>, state: Data<&Arc<AppState>>,
) -> Result<WithHeader<WithContentType<Vec<u8>>>, poem::Error> { ) -> Result<Response, poem::Error> {
let book = state let book = state
.calibre .calibre
.scalar_book(id) .scalar_book(id)
@ -33,13 +37,14 @@ pub async fn handler(
.library_path .library_path
.join(book.data.path) .join(book.data.path)
.join(file_name); .join(file_name);
let mut file = File::open(file_path).map_err(|_| NotFoundError)?;
let mut data = Vec::new(); let mut file = File::open(file_path).await.map_err(|_| NotFoundError)?;
file.read_to_end(&mut data).map_err(|_| NotFoundError)?; let stream = ReaderStream::new(file);
let content_type = format.0; let body = Body::from_bytes_stream(stream);
Ok(data let content_type: MediaType = format.into();
.with_content_type(content_type) Ok(body
.with_header("Content-Disposition", format!("filename={file_name};"))) .with_content_type(format!("{content_type}"))
.with_header("Content-Disposition", format!("filename=\"{file_name}\""))
.into_response())
} }