common download handler
This commit is contained in:
parent
13aae44163
commit
ee764ca4ca
@ -4,12 +4,20 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use calibre_db::data::pagination::SortOrder;
|
use calibre_db::data::pagination::SortOrder;
|
||||||
use poem::{
|
use poem::{
|
||||||
|
error::NotFoundError,
|
||||||
handler,
|
handler,
|
||||||
web::{Data, Path},
|
web::{Data, Path},
|
||||||
Response,
|
Response,
|
||||||
};
|
};
|
||||||
|
use tokio::fs::File;
|
||||||
|
|
||||||
use crate::{app_state::AppState, Accept};
|
use crate::{
|
||||||
|
app_state::AppState,
|
||||||
|
data::book::{Book, Format},
|
||||||
|
handlers::error::HandlerError,
|
||||||
|
opds::media_type::MediaType,
|
||||||
|
Accept,
|
||||||
|
};
|
||||||
|
|
||||||
/// Handle a request for multiple books, starting at the first.
|
/// Handle a request for multiple books, starting at the first.
|
||||||
#[handler]
|
#[handler]
|
||||||
@ -31,6 +39,31 @@ pub async fn handler(
|
|||||||
books(&accept, &state, Some(&cursor), &sort_order).await
|
books(&accept, &state, Some(&cursor), &sort_order).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Handle a request for a book with id `id` in format `format`.
|
||||||
|
#[handler]
|
||||||
|
pub async fn handler_download(
|
||||||
|
Path((id, format)): Path<(u64, String)>,
|
||||||
|
state: Data<&Arc<AppState>>,
|
||||||
|
) -> Result<Response, poem::Error> {
|
||||||
|
let book = state
|
||||||
|
.calibre
|
||||||
|
.scalar_book(id)
|
||||||
|
.map_err(HandlerError::DataError)?;
|
||||||
|
let book = Book::full_book(&book, &state).ok_or(NotFoundError)?;
|
||||||
|
let format = Format(format);
|
||||||
|
let file_name = book.formats.get(&format).ok_or(NotFoundError)?;
|
||||||
|
let file_path = state
|
||||||
|
.config
|
||||||
|
.library_path
|
||||||
|
.join(book.data.path)
|
||||||
|
.join(file_name);
|
||||||
|
let mut file = File::open(file_path).await.map_err(|_| NotFoundError)?;
|
||||||
|
let content_type: MediaType = format.into();
|
||||||
|
let content_type = format!("{content_type}");
|
||||||
|
|
||||||
|
crate::handlers::download::handler(file_name, file, &content_type).await
|
||||||
|
}
|
||||||
|
|
||||||
async fn books(
|
async fn books(
|
||||||
accept: &Accept,
|
accept: &Accept,
|
||||||
state: &Arc<AppState>,
|
state: &Arc<AppState>,
|
||||||
|
@ -2,16 +2,14 @@
|
|||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::{app_state::AppState, handlers::error::HandlerError};
|
||||||
use poem::{
|
use poem::{
|
||||||
error::NotFoundError,
|
error::NotFoundError,
|
||||||
handler,
|
handler,
|
||||||
web::{headers::ContentType, Data, Path},
|
web::{headers::ContentType, Data, Path},
|
||||||
Body, IntoResponse, Response,
|
Response,
|
||||||
};
|
};
|
||||||
use tokio::fs::File;
|
use tokio::fs::File;
|
||||||
use tokio_util::io::ReaderStream;
|
|
||||||
|
|
||||||
use crate::{app_state::AppState, handlers::error::HandlerError};
|
|
||||||
|
|
||||||
/// Handle a request for the cover image of book with id `id`.
|
/// Handle a request for the cover image of book with id `id`.
|
||||||
#[handler]
|
#[handler]
|
||||||
@ -22,11 +20,5 @@ pub async fn handler(id: Path<u64>, state: Data<&Arc<AppState>>) -> Result<Respo
|
|||||||
.map_err(HandlerError::DataError)?;
|
.map_err(HandlerError::DataError)?;
|
||||||
let cover_path = state.config.library_path.join(book.path).join("cover.jpg");
|
let cover_path = state.config.library_path.join(book.path).join("cover.jpg");
|
||||||
let mut cover = File::open(cover_path).await.map_err(|_| NotFoundError)?;
|
let mut cover = File::open(cover_path).await.map_err(|_| NotFoundError)?;
|
||||||
let stream = ReaderStream::new(cover);
|
crate::handlers::download::handler("cover.jpg", cover, &ContentType::jpeg().to_string()).await
|
||||||
let body = Body::from_bytes_stream(stream);
|
|
||||||
|
|
||||||
Ok(body
|
|
||||||
.with_content_type(ContentType::jpeg().to_string())
|
|
||||||
.with_header("Content-Disposition", "filename=\"cover.jpg\"")
|
|
||||||
.into_response())
|
|
||||||
}
|
}
|
||||||
|
@ -1,50 +1,23 @@
|
|||||||
//! Handle requests for specific formats of a book.
|
//! Handle requests for specific formats of a book.
|
||||||
|
|
||||||
use std::sync::Arc;
|
use tokio::io::AsyncRead;
|
||||||
|
|
||||||
use tokio::fs::File;
|
use poem::{Body, IntoResponse, Response};
|
||||||
|
|
||||||
use poem::{
|
|
||||||
error::NotFoundError,
|
|
||||||
handler,
|
|
||||||
web::{Data, Path},
|
|
||||||
Body, IntoResponse, Response,
|
|
||||||
};
|
|
||||||
use tokio_util::io::ReaderStream;
|
use tokio_util::io::ReaderStream;
|
||||||
|
|
||||||
use crate::{
|
/// Handle a request for file.
|
||||||
app_state::AppState,
|
///
|
||||||
data::book::{Book, Format},
|
/// Must not be used directly from a route as that makes it vulnerable to path traversal attacks.
|
||||||
handlers::error::HandlerError,
|
pub async fn handler<A: AsyncRead + Send + 'static>(
|
||||||
opds::media_type::MediaType,
|
file_name: &str,
|
||||||
};
|
reader: A,
|
||||||
|
content_type: &str,
|
||||||
/// Handle a request for a book with id `id` in format `format`.
|
|
||||||
#[handler]
|
|
||||||
pub async fn handler(
|
|
||||||
Path((id, format)): Path<(u64, String)>,
|
|
||||||
state: Data<&Arc<AppState>>,
|
|
||||||
) -> Result<Response, poem::Error> {
|
) -> Result<Response, poem::Error> {
|
||||||
let book = state
|
let stream = ReaderStream::new(reader);
|
||||||
.calibre
|
|
||||||
.scalar_book(id)
|
|
||||||
.map_err(HandlerError::DataError)?;
|
|
||||||
let book = Book::full_book(&book, &state).ok_or(NotFoundError)?;
|
|
||||||
let format = Format(format);
|
|
||||||
let file_name = book.formats.get(&format).ok_or(NotFoundError)?;
|
|
||||||
let file_path = state
|
|
||||||
.config
|
|
||||||
.library_path
|
|
||||||
.join(book.data.path)
|
|
||||||
.join(file_name);
|
|
||||||
|
|
||||||
let mut file = File::open(file_path).await.map_err(|_| NotFoundError)?;
|
|
||||||
let stream = ReaderStream::new(file);
|
|
||||||
let body = Body::from_bytes_stream(stream);
|
let body = Body::from_bytes_stream(stream);
|
||||||
|
|
||||||
let content_type: MediaType = format.into();
|
|
||||||
Ok(body
|
Ok(body
|
||||||
.with_content_type(format!("{content_type}"))
|
.with_content_type(content_type)
|
||||||
.with_header("Content-Disposition", format!("filename=\"{file_name}\""))
|
.with_header("Content-Disposition", format!("filename=\"{file_name}\""))
|
||||||
.into_response())
|
.into_response())
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,11 @@
|
|||||||
use crate::{APP_NAME, VERSION};
|
use crate::{APP_NAME, VERSION};
|
||||||
use poem::{handler, Body, IntoResponse, Response};
|
use poem::{handler, Response};
|
||||||
use tokio_util::io::ReaderStream;
|
|
||||||
|
|
||||||
const SOURCE_ARCHIVE: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/archive.zip"));
|
const SOURCE_ARCHIVE: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/archive.zip"));
|
||||||
|
|
||||||
/// Handle a request for source code of the server..
|
/// Handle a request for source code of the server..
|
||||||
#[handler]
|
#[handler]
|
||||||
pub async fn handler() -> Result<Response, poem::Error> {
|
pub async fn handler() -> Result<Response, poem::Error> {
|
||||||
let stream = ReaderStream::new(SOURCE_ARCHIVE);
|
let file_name = format!("{APP_NAME}-{VERSION}.zip");
|
||||||
let body = Body::from_bytes_stream(stream);
|
crate::handlers::download::handler(&file_name, SOURCE_ARCHIVE, "application/zip").await
|
||||||
|
|
||||||
Ok(body
|
|
||||||
.with_content_type("application/zip")
|
|
||||||
.with_header(
|
|
||||||
"Content-Disposition",
|
|
||||||
format!("filename=\"{APP_NAME}-{VERSION}.zip\""),
|
|
||||||
)
|
|
||||||
.into_response())
|
|
||||||
}
|
}
|
||||||
|
@ -109,7 +109,7 @@ pub async fn run(config: Config) -> Result<(), std::io::Error> {
|
|||||||
get(handlers::authors::handler),
|
get(handlers::authors::handler),
|
||||||
)
|
)
|
||||||
.at("/cover/:id", get(handlers::cover::handler))
|
.at("/cover/:id", get(handlers::cover::handler))
|
||||||
.at("/book/:id/:format", get(handlers::download::handler))
|
.at("/book/:id/:format", get(handlers::books::handler_download))
|
||||||
.at("/archive", get(handlers::source_archive::handler))
|
.at("/archive", get(handlers::source_archive::handler))
|
||||||
.nest("/static", EmbeddedFilesEndpoint::<Files>::new())
|
.nest("/static", EmbeddedFilesEndpoint::<Files>::new())
|
||||||
.data(Accept::Html);
|
.data(Accept::Html);
|
||||||
|
Loading…
Reference in New Issue
Block a user