Compare commits
No commits in common. "f4ee1c19ec6e6b59b031e6e4ae90eb7cab24e52e" and "8f698c0e7df329dcc24bb008cd972a63f25a9fc7" have entirely different histories.
f4ee1c19ec
...
8f698c0e7d
@ -12,7 +12,7 @@ insist on writing my own containers). _How hard can it be_ I thought and went on
|
|||||||
hacking something together. The result does at most one tenth of what cops can
|
hacking something together. The result does at most one tenth of what cops can
|
||||||
do but luckily enough it is the part I need for myself.
|
do but luckily enough it is the part I need for myself.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
# Building
|
# Building
|
||||||
|
|
||||||
|
@ -4,20 +4,12 @@ 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::{
|
use crate::{app_state::AppState, Accept};
|
||||||
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]
|
||||||
@ -39,31 +31,6 @@ 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>,
|
||||||
|
@ -1,24 +1,31 @@
|
|||||||
//! Handle requests for cover images.
|
//! Handle requests for cover images.
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::{fs::File, io::Read, 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, WithContentType},
|
||||||
Response,
|
IntoResponse,
|
||||||
};
|
};
|
||||||
use tokio::fs::File;
|
|
||||||
|
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]
|
||||||
pub async fn handler(id: Path<u64>, state: Data<&Arc<AppState>>) -> Result<Response, poem::Error> {
|
pub async fn handler(
|
||||||
|
id: Path<u64>,
|
||||||
|
state: Data<&Arc<AppState>>,
|
||||||
|
) -> Result<WithContentType<Vec<u8>>, poem::Error> {
|
||||||
let book = state
|
let book = state
|
||||||
.calibre
|
.calibre
|
||||||
.scalar_book(*id)
|
.scalar_book(*id)
|
||||||
.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).map_err(|_| NotFoundError)?;
|
||||||
crate::handlers::download::handler("cover.jpg", cover, &ContentType::jpeg().to_string()).await
|
|
||||||
|
let mut data = Vec::new();
|
||||||
|
cover.read_to_end(&mut data).map_err(|_| NotFoundError)?;
|
||||||
|
|
||||||
|
Ok(data.with_content_type(ContentType::jpeg().to_string()))
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,50 @@
|
|||||||
//! Handle requests for specific formats of a book.
|
//! Handle requests for specific formats of a book.
|
||||||
|
|
||||||
use tokio::io::AsyncRead;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use poem::{Body, IntoResponse, Response};
|
use tokio::fs::File;
|
||||||
|
|
||||||
|
use poem::{
|
||||||
|
error::NotFoundError,
|
||||||
|
handler,
|
||||||
|
web::{Data, Path},
|
||||||
|
Body, IntoResponse, Response,
|
||||||
|
};
|
||||||
use tokio_util::io::ReaderStream;
|
use tokio_util::io::ReaderStream;
|
||||||
|
|
||||||
/// Handle a request for file.
|
use crate::{
|
||||||
///
|
app_state::AppState,
|
||||||
/// Must not be used directly from a route as that makes it vulnerable to path traversal attacks.
|
data::book::{Book, Format},
|
||||||
pub async fn handler<A: AsyncRead + Send + 'static>(
|
handlers::error::HandlerError,
|
||||||
file_name: &str,
|
opds::media_type::MediaType,
|
||||||
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 stream = ReaderStream::new(reader);
|
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 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(content_type)
|
.with_content_type(format!("{content_type}"))
|
||||||
.with_header("Content-Disposition", format!("filename=\"{file_name}\""))
|
.with_header("Content-Disposition", format!("filename=\"{file_name}\""))
|
||||||
.into_response())
|
.into_response())
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,20 @@
|
|||||||
use crate::{APP_NAME, VERSION};
|
use crate::{APP_NAME, VERSION};
|
||||||
use poem::{handler, Response};
|
use poem::{handler, Body, IntoResponse, 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 file_name = format!("{APP_NAME}-{VERSION}.zip");
|
let stream = ReaderStream::new(SOURCE_ARCHIVE);
|
||||||
crate::handlers::download::handler(&file_name, SOURCE_ARCHIVE, "application/zip").await
|
let body = Body::from_bytes_stream(stream);
|
||||||
|
|
||||||
|
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::books::handler_download))
|
.at("/book/:id/:format", get(handlers::download::handler))
|
||||||
.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);
|
||||||
|
BIN
screenshot.jpg
BIN
screenshot.jpg
Binary file not shown.
Before Width: | Height: | Size: 62 KiB |
BIN
screenshot.png
Normal file
BIN
screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 675 KiB |
Loading…
Reference in New Issue
Block a user