diff --git a/cops-web/src/data/book.rs b/cops-web/src/data/book.rs index 1f71614..bae8a8b 100644 --- a/cops-web/src/data/book.rs +++ b/cops-web/src/data/book.rs @@ -1,6 +1,10 @@ +use std::{collections::HashMap, path::Path}; + use calibre_db::data::{book::Book as DbBook, series::Series as DbSeries}; use serde::Serialize; +use crate::app_state::AppState; + #[derive(Debug, Serialize)] pub struct Book { pub id: u64, @@ -9,6 +13,7 @@ pub struct Book { pub path: String, pub author: String, pub series: Option<(String, f64)>, + pub formats: HashMap, } impl Book { @@ -16,6 +21,7 @@ impl Book { db_book: &DbBook, db_series: Option<(DbSeries, f64)>, author: &str, + formats: HashMap, ) -> Self { Self { id: db_book.id, @@ -24,6 +30,34 @@ impl Book { path: db_book.path.clone(), author: author.to_string(), series: db_series.map(|x| (x.0.name, x.1)), + formats, } } + + fn formats(book: &DbBook, library_path: &Path) -> HashMap { + let book_path = library_path.join(&book.path); + let mut formats = HashMap::new(); + + for entry in book_path.read_dir().unwrap().flatten() { + if let Some(extension) = entry.path().extension() { + let format = match extension.to_string_lossy().to_string().as_str() { + "pdf" => Some("pdf".to_string()), + "epub" => Some("epub".to_string()), + _ => None, + }; + if let Some(format) = format { + formats.insert(format, entry.file_name().to_string_lossy().to_string()); + } + } + } + + formats + } + + pub fn full_book(book: &DbBook, state: &AppState) -> Option { + let formats = Book::formats(book, &state.config.library_path); + let author = state.calibre.book_author(book.id).ok()?; + let series = state.calibre.book_series(book.id).ok()?; + Some(Book::from_db_book(book, series, &author.name, formats)) + } } diff --git a/cops-web/src/handlers/cover.rs b/cops-web/src/handlers/cover.rs index dcdabca..fb40428 100644 --- a/cops-web/src/handlers/cover.rs +++ b/cops-web/src/handlers/cover.rs @@ -1,23 +1,25 @@ use std::{fs::File, io::Read, sync::Arc}; use poem::{ + error::NotFoundError, handler, web::{headers::ContentType, Data, Path, WithContentType}, IntoResponse, }; -use crate::app_state::AppState; +use crate::{app_state::AppState, handlers::error::SqliteError}; #[handler] pub async fn handler( id: Path, state: Data<&Arc>, ) -> Result>, poem::Error> { - let book = state.calibre.scalar_book(*id).unwrap(); + let book = state.calibre.scalar_book(*id).map_err(SqliteError)?; let cover_path = state.config.library_path.join(book.path).join("cover.jpg"); - let mut cover = File::open(cover_path).unwrap(); + let mut cover = File::open(cover_path).map_err(|_| NotFoundError)?; + let mut data = Vec::new(); - cover.read_to_end(&mut data).unwrap(); + cover.read_to_end(&mut data).map_err(|_| NotFoundError)?; Ok(data.with_content_type(ContentType::jpeg().to_string())) } diff --git a/cops-web/src/handlers/download.rs b/cops-web/src/handlers/download.rs new file mode 100644 index 0000000..5c7dcbb --- /dev/null +++ b/cops-web/src/handlers/download.rs @@ -0,0 +1,35 @@ +use std::{fs::File, io::Read, sync::Arc}; + +use poem::{ + error::NotFoundError, + handler, + web::{Data, Path, WithContentType, WithHeader}, + IntoResponse, +}; + +use crate::{app_state::AppState, data::book::Book, handlers::error::SqliteError}; + +#[handler] +pub async fn handler( + Path((id, format)): Path<(u64, String)>, + state: Data<&Arc>, +) -> Result>>, poem::Error> { + let book = state.calibre.scalar_book(id).map_err(SqliteError)?; + let book = Book::full_book(&book, &state).ok_or(NotFoundError)?; + let format: &str = format.as_str(); + let file_name = book.formats.get(format).ok_or(NotFoundError)?; + let file_path = state.config.library_path.join(book.path).join(file_name); + let mut file = File::open(file_path).map_err(|_| NotFoundError)?; + + let mut data = Vec::new(); + file.read_to_end(&mut data).map_err(|_| NotFoundError)?; + let content_type = match format { + "pdf" => "application/pdf", + "epub" => "application/epub+zip", + _ => unreachable!(), + }; + + Ok(data + .with_content_type(content_type) + .with_header("Content-Disposition", format!("filename={file_name};"))) +} diff --git a/cops-web/src/handlers/recents.rs b/cops-web/src/handlers/recents.rs index 1f8faa0..793086c 100644 --- a/cops-web/src/handlers/recents.rs +++ b/cops-web/src/handlers/recents.rs @@ -16,11 +16,7 @@ pub async fn handler(state: Data<&Arc>) -> Result, poem:: let recent_books = state.calibre.recent_books(50).map_err(SqliteError)?; let recent_books = recent_books .iter() - .filter_map(|x| { - let author = state.calibre.book_author(x.id).ok()?; - let series = state.calibre.book_series(x.id).ok()?; - Some(Book::from_db_book(x, series, &author.name)) - }) + .filter_map(|x| Book::full_book(x, &state)) .collect::>(); let mut context = Context::new(); diff --git a/cops-web/src/main.rs b/cops-web/src/main.rs index 39bc97f..63aaea7 100644 --- a/cops-web/src/main.rs +++ b/cops-web/src/main.rs @@ -19,6 +19,7 @@ mod data { } mod handlers { pub mod cover; + pub mod download; pub mod error; pub mod recents; } @@ -44,6 +45,7 @@ async fn main() -> Result<(), std::io::Error> { let app = Route::new() .at("/", get(handlers::recents::handler)) .at("/cover/:id", get(handlers::cover::handler)) + .at("/book/:id/:format", get(handlers::download::handler)) .nest("/static", EmbeddedFilesEndpoint::::new()) .data(app_state); diff --git a/cops-web/src/templates.rs b/cops-web/src/templates.rs index d236044..6a14a14 100644 --- a/cops-web/src/templates.rs +++ b/cops-web/src/templates.rs @@ -5,6 +5,7 @@ pub static TEMPLATES: Lazy = Lazy::new(|| { let mut tera = Tera::default(); tera.add_raw_templates(vec![ ("base", include_str!("../templates/base.html")), + ("book_card", include_str!("../templates/book_card.html")), ("recents", include_str!("../templates/recents.html")), ]) .expect("failed to parse tera templates"); diff --git a/cops-web/templates/book_card.html b/cops-web/templates/book_card.html new file mode 100644 index 0000000..f9148b3 --- /dev/null +++ b/cops-web/templates/book_card.html @@ -0,0 +1,25 @@ +
+
+
+
{{ book.title }}
+

{{ book.author }}

+
+
+ book cover +
+
+
+ +
+
+
+
diff --git a/cops-web/templates/recents.html b/cops-web/templates/recents.html index 539586f..a292932 100644 --- a/cops-web/templates/recents.html +++ b/cops-web/templates/recents.html @@ -2,32 +2,8 @@ {% block content %}

Recent Books

- {% for book in books %} -
-
-
-
{{ book.title }}
- -

{{ book.author }}

-
-
- book cover - -
- {% endfor %} + {% for book in books %} + {% include "book_card" %} + {% endfor %}
{% endblock content %}