perf(api,indexer): optimiser pages, thumbnails, watcher et robustesse fd
- Pages: mode Original (zero-transcoding), ETag/304, cache index CBZ, préfetch next 2 pages, filtre Triangle par défaut - Thumbnails: DCT scaling JPEG via jpeg-decoder (decode 7x plus rapide), img.thumbnail() pour resize, support format Original, fix JPEG RGBA8 - API fallback thumbnail: OutputFormat::Original + DCT scaling au lieu de WebP full-decode, retour (bytes, content_type) dynamique - Watcher: remplacement notify par poll léger sans inotify/fd, skip poll quand job actif, snapshots en mémoire - Jobs: mutex exclusif corrigé (tous statuts actifs, tous types exclusifs) - Robustesse: suppression fs::canonicalize (problèmes fd Docker), list_folders avec erreurs explicites, has_children default true - Backoffice: FormRow items-start pour alignement inputs avec helper text, labels settings clarifiés Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -584,6 +584,17 @@ use axum::{
|
||||
response::IntoResponse,
|
||||
};
|
||||
|
||||
/// Detect content type from thumbnail file extension.
|
||||
fn detect_thumbnail_content_type(path: &str) -> &'static str {
|
||||
if path.ends_with(".jpg") || path.ends_with(".jpeg") {
|
||||
"image/jpeg"
|
||||
} else if path.ends_with(".png") {
|
||||
"image/png"
|
||||
} else {
|
||||
"image/webp"
|
||||
}
|
||||
}
|
||||
|
||||
/// Get book thumbnail image
|
||||
#[utoipa::path(
|
||||
get,
|
||||
@@ -612,9 +623,12 @@ pub async fn get_thumbnail(
|
||||
let row = row.ok_or_else(|| ApiError::not_found("book not found"))?;
|
||||
let thumbnail_path: Option<String> = row.get("thumbnail_path");
|
||||
|
||||
let data = if let Some(ref path) = thumbnail_path {
|
||||
let (data, content_type) = if let Some(ref path) = thumbnail_path {
|
||||
match std::fs::read(path) {
|
||||
Ok(bytes) => bytes,
|
||||
Ok(bytes) => {
|
||||
let ct = detect_thumbnail_content_type(path);
|
||||
(bytes, ct)
|
||||
}
|
||||
Err(_) => {
|
||||
// File missing on disk (e.g. different mount in dev) — fall back to live render
|
||||
crate::pages::render_book_page_1(&state, book_id, 300, 80).await?
|
||||
@@ -626,7 +640,7 @@ pub async fn get_thumbnail(
|
||||
};
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(header::CONTENT_TYPE, HeaderValue::from_static("image/webp"));
|
||||
headers.insert(header::CONTENT_TYPE, HeaderValue::from_static(content_type));
|
||||
headers.insert(
|
||||
header::CACHE_CONTROL,
|
||||
HeaderValue::from_static("public, max-age=31536000, immutable"),
|
||||
|
||||
Reference in New Issue
Block a user