From 7e4a40e30c1d20c8e577cf950f126f9433b25846 Mon Sep 17 00:00:00 2001 From: Aditya Chandel <8075870+adityachandelgit@users.noreply.github.com> Date: Sun, 5 Oct 2025 11:12:18 -0600 Subject: [PATCH] Resolve CBX page sequence issue (#1270) --- .../controller/BookMediaController.java | 1 - .../service/reader/CbxReaderService.java | 61 ++++++++++++------- 2 files changed, 40 insertions(+), 22 deletions(-) diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/controller/BookMediaController.java b/booklore-api/src/main/java/com/adityachandel/booklore/controller/BookMediaController.java index 5d42ff6ee..3234c0b1c 100644 --- a/booklore-api/src/main/java/com/adityachandel/booklore/controller/BookMediaController.java +++ b/booklore-api/src/main/java/com/adityachandel/booklore/controller/BookMediaController.java @@ -25,7 +25,6 @@ import java.io.IOException; public class BookMediaController { private final BookService bookService; - private final BookMetadataService bookMetadataService; private final PdfReaderService pdfReaderService; private final CbxReaderService cbxReaderService; private final BookDropService bookDropService; diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/service/reader/CbxReaderService.java b/booklore-api/src/main/java/com/adityachandel/booklore/service/reader/CbxReaderService.java index 530063ce1..7cf21605d 100644 --- a/booklore-api/src/main/java/com/adityachandel/booklore/service/reader/CbxReaderService.java +++ b/booklore-api/src/main/java/com/adityachandel/booklore/service/reader/CbxReaderService.java @@ -40,6 +40,7 @@ public class CbxReaderService { private static final String CBZ_EXTENSION = ".cbz"; private static final String CBR_EXTENSION = ".cbr"; private static final String CB7_EXTENSION = ".cb7"; + private static final String[] SUPPORTED_IMAGE_EXTENSIONS = {".jpg", ".jpeg", ".png", ".webp"}; private final BookRepository bookRepository; private final AppSettingService appSettingService; @@ -81,11 +82,13 @@ public class CbxReaderService { } try (var stream = Files.list(cacheDir)) { - return stream - .filter(p -> p.toString().endsWith(".jpg")) + List imageFiles = stream + .filter(p -> isImageFile(p.getFileName().toString())) .sorted(Comparator.comparing(Path::getFileName)) - .map(p -> extractPageNumber(p.getFileName().toString())) - .filter(p -> p != -1) + .toList(); + + return java.util.stream.IntStream.rangeClosed(1, imageFiles.size()) + .boxed() .collect(Collectors.toList()); } catch (IOException e) { log.error("Failed to list pages for book {}", bookId, e); @@ -94,8 +97,21 @@ public class CbxReaderService { } public void streamPageImage(Long bookId, int page, OutputStream outputStream) throws IOException { - Path pagePath = Path.of(fileService.getCbxCachePath(), String.valueOf(bookId), String.format("%04d.jpg", page)); - if (!Files.exists(pagePath)) throw new FileNotFoundException("Page not found: " + page); + Path bookDir = Path.of(fileService.getCbxCachePath(), String.valueOf(bookId)); + List images; + try (Stream files = Files.list(bookDir)) { + images = files + .filter(p -> isImageFile(p.getFileName().toString())) + .sorted(Comparator.comparing(p -> p.getFileName().toString())) + .toList(); + } + if (images.isEmpty()) { + throw new FileNotFoundException("No image files found for book: " + bookId); + } + if (page < 1 || page > images.size()) { + throw new FileNotFoundException("Page out of range: " + page); + } + Path pagePath = images.get(page - 1); try (InputStream in = Files.newInputStream(pagePath)) { IOUtils.copy(in, outputStream); } @@ -119,12 +135,11 @@ public class CbxReaderService { ZipInputStream zis = new ZipInputStream(fis)) { ZipEntry entry; - int index = 1; while ((entry = zis.getNextEntry()) != null) { - isImageFile(entry.getName()); if (!entry.isDirectory() && isImageFile(entry.getName())) { - Path target = targetDir.resolve(String.format("%04d.jpg", index++)); + String fileName = extractFileNameFromPath(entry.getName()); + Path target = targetDir.resolve(fileName); Files.copy(zis, target, StandardCopyOption.REPLACE_EXISTING); } zis.closeEntry(); @@ -135,10 +150,10 @@ public class CbxReaderService { private void extract7zArchive(Path cb7Path, Path targetDir) throws IOException { try (SevenZFile sevenZFile = new SevenZFile(cb7Path.toFile())) { SevenZArchiveEntry entry; - int index = 1; while ((entry = sevenZFile.getNextEntry()) != null) { if (!entry.isDirectory() && isImageFile(entry.getName())) { - Path target = targetDir.resolve(String.format("%04d.jpg", index++)); + String fileName = extractFileNameFromPath(entry.getName()); + Path target = targetDir.resolve(fileName); try (OutputStream out = Files.newOutputStream(target)) { copySevenZEntry(sevenZFile, out, entry.getSize()); } @@ -160,10 +175,10 @@ public class CbxReaderService { private void extractRarArchive(Path cbrPath, Path targetDir) throws IOException { try (Archive archive = new Archive(cbrPath.toFile())) { List headers = archive.getFileHeaders(); - int index = 1; for (FileHeader header : headers) { if (!header.isDirectory() && isImageFile(header.getFileName())) { - Path target = targetDir.resolve(String.format("%04d.jpg", index++)); + String fileName = extractFileNameFromPath(header.getFileName()); + Path target = targetDir.resolve(fileName); try (OutputStream out = Files.newOutputStream(target)) { archive.extractFile(header, out); } @@ -174,19 +189,23 @@ public class CbxReaderService { } } - private boolean isImageFile(String name) { - String lower = name.toLowerCase().replace("\\", "/"); - return lower.endsWith(".jpg") || lower.endsWith(".jpeg") || lower.endsWith(".png") || lower.endsWith(".webp"); + private String extractFileNameFromPath(String fullPath) { + String normalizedPath = fullPath.replace("\\", "/"); + int lastSlash = normalizedPath.lastIndexOf('/'); + return lastSlash >= 0 ? normalizedPath.substring(lastSlash + 1) : normalizedPath; } - private int extractPageNumber(String filename) { - try { - return Integer.parseInt(filename.replaceAll("\\D+", "")); - } catch (Exception e) { - return -1; + private boolean isImageFile(String name) { + String lower = name.toLowerCase().replace("\\", "/"); + for (String extension : SUPPORTED_IMAGE_EXTENSIONS) { + if (lower.endsWith(extension)) { + return true; + } } + return false; } + private boolean needsCacheRefresh(Path cbzPath, Path cacheInfoPath) throws IOException { if (!Files.exists(cacheInfoPath)) return true;