Compare commits

...

2 Commits

Author SHA1 Message Date
7d3670e951 fix(api/pages): fallback CBR→ZIP et CBZ→RAR pour archives mal extensionnées
Même correctif que dans le parsers/indexer : un .cbr qui est en réalité
un ZIP (et vice-versa) retourne maintenant la bonne page au lieu d'un 500.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 23:06:40 +01:00
09682f5836 fix(docker): augmenter les limites inotify pour éviter "Too many open files"
Ajoute les sysctls fs.inotify.max_user_watches=524288 et
fs.inotify.max_user_instances=512 sur le service indexer pour
prévenir l'erreur watcher sur les grosses bibliothèques.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 23:04:27 +01:00
2 changed files with 29 additions and 12 deletions

View File

@@ -352,8 +352,8 @@ fn render_page(
filter: image::imageops::FilterType,
) -> Result<Vec<u8>, ApiError> {
let page_bytes = match input_format {
"cbz" => extract_cbz_page(abs_path, page_number)?,
"cbr" => extract_cbr_page(abs_path, page_number)?,
"cbz" => extract_cbz_page(abs_path, page_number, true)?,
"cbr" => extract_cbr_page(abs_path, page_number, true)?,
"pdf" => render_pdf_page(abs_path, page_number, width)?,
_ => return Err(ApiError::bad_request("unsupported source format")),
};
@@ -361,7 +361,7 @@ fn render_page(
transcode_image(&page_bytes, out_format, quality, width, filter)
}
fn extract_cbz_page(abs_path: &str, page_number: u32) -> Result<Vec<u8>, ApiError> {
fn extract_cbz_page(abs_path: &str, page_number: u32, allow_fallback: bool) -> Result<Vec<u8>, ApiError> {
debug!("Opening CBZ archive: {}", abs_path);
let file = std::fs::File::open(abs_path).map_err(|e| {
if e.kind() == std::io::ErrorKind::NotFound {
@@ -371,11 +371,18 @@ fn extract_cbz_page(abs_path: &str, page_number: u32) -> Result<Vec<u8>, ApiErro
ApiError::internal(format!("cannot open cbz: {e}"))
}
})?;
let mut archive = zip::ZipArchive::new(file).map_err(|e| {
error!("Invalid CBZ archive {}: {}", abs_path, e);
ApiError::internal(format!("invalid cbz: {e}"))
})?;
let mut archive = match zip::ZipArchive::new(file) {
Ok(a) => a,
Err(zip_err) => {
if allow_fallback {
warn!("CBZ open failed for {}, trying RAR fallback: {}", abs_path, zip_err);
return extract_cbr_page(abs_path, page_number, false);
}
error!("Invalid CBZ archive {}: {}", abs_path, zip_err);
return Err(ApiError::internal(format!("invalid cbz: {zip_err}")));
}
};
let mut image_names: Vec<String> = Vec::new();
for i in 0..archive.len() {
@@ -410,15 +417,22 @@ fn extract_cbz_page(abs_path: &str, page_number: u32) -> Result<Vec<u8>, ApiErro
Ok(buf)
}
fn extract_cbr_page(abs_path: &str, page_number: u32) -> Result<Vec<u8>, ApiError> {
fn extract_cbr_page(abs_path: &str, page_number: u32, allow_fallback: bool) -> Result<Vec<u8>, ApiError> {
info!("Opening CBR archive: {}", abs_path);
let index = page_number as usize - 1;
// Pass 1: list all image names (in-process, no subprocess)
let mut image_names: Vec<String> = {
let archive = unrar::Archive::new(abs_path)
.open_for_listing()
.map_err(|e| ApiError::internal(format!("unrar listing failed: {}", e)))?;
let archive = match unrar::Archive::new(abs_path).open_for_listing() {
Ok(a) => a,
Err(e) => {
if allow_fallback {
warn!("CBR open failed for {}, trying ZIP fallback: {}", abs_path, e);
return extract_cbz_page(abs_path, page_number, false);
}
return Err(ApiError::internal(format!("unrar listing failed: {}", e)));
}
};
let mut names = Vec::new();
for entry in archive {
let entry = entry.map_err(|e| ApiError::internal(format!("unrar entry error: {}", e)))?;

View File

@@ -62,6 +62,9 @@ services:
volumes:
- ${LIBRARIES_HOST_PATH:-./libraries}:/libraries
- ${THUMBNAILS_HOST_PATH:-./data/thumbnails}:/data/thumbnails
sysctls:
- fs.inotify.max_user_watches=524288
- fs.inotify.max_user_instances=512
depends_on:
api:
condition: service_healthy