entityManager->getRepository(ChapterEntity::class)->findOneBy([ 'id' => $chapterId->getValue() ]); $pagesDirectory = $chapter->getPagesDirectory(); if ($pagesDirectory && is_dir($pagesDirectory)) { return $this->getPagesFromDirectory($chapterId, $pagesDirectory, $page, $itemsPerPage); } $cbzPath = $chapter->getCbzPath(); if (!$cbzPath) { return []; } return $this->getPagesFromCbz($chapterId, $cbzPath, $page, $itemsPerPage); } public function getChapterContext(ChapterId $chapterId): ChapterContext { /** @var ChapterEntity $chapter */ $chapter = $this->entityManager->getRepository(ChapterEntity::class)->findOneBy([ 'id' => $chapterId->getValue() ]); if (!$chapter) { throw ChapterNotFoundException::forChapter($chapterId); } return new ChapterContext( id: $chapterId, previousChapterId: $this->getPreviousChapterId($chapterId), nextChapterId: $this->getNextChapterId($chapterId), mangaTitle: $chapter->getManga()->getTitle(), number: $chapter->getNumber(), chapterTitle: $chapter->getTitle(), cbzPath: $chapter->getCbzPath(), volume: $chapter->getVolume(), totalPages: 0, isVisible: $chapter->isVisible(), createdAt: new \DateTimeImmutable(), pagesDirectory: $chapter->getPagesDirectory(), ); } public function getTotalPagesForChapter(ChapterId $chapterId): int { $chapter = $this->entityManager->getRepository(ChapterEntity::class)->findOneBy([ 'id' => $chapterId->getValue() ]); if (!$chapter) { throw ChapterNotFoundException::forChapter($chapterId); } $pagesDirectory = $chapter->getPagesDirectory(); if ($pagesDirectory && is_dir($pagesDirectory)) { return count($this->getImageFiles($pagesDirectory)); } $cbzPath = $chapter->getCbzPath(); if (!$cbzPath) { return 0; } $zip = new ZipArchive(); $zip->open($cbzPath); $count = $zip->numFiles; $zip->close(); return $count; } public function getPreviousChapterId(ChapterId $chapterId): ?ChapterId { $currentChapter = $this->entityManager->getRepository(ChapterEntity::class)->findOneBy([ 'id' => $chapterId->getValue() ]); $qb = $this->entityManager->createQueryBuilder(); $qb->select('c') ->from(ChapterEntity::class, 'c') ->where('c.manga = :manga') ->andWhere('c.number < :number') ->andWhere('c.visible = true') ->andWhere('c.pagesDirectory IS NOT NULL OR c.cbzPath IS NOT NULL') ->orderBy('c.number', 'DESC') ->setMaxResults(1) ->setParameters([ 'manga' => $currentChapter->getManga(), 'number' => $currentChapter->getNumber() ]); $previousChapter = $qb->getQuery()->getOneOrNullResult(); return $previousChapter ? new ChapterId((string) $previousChapter->getId()) : null; } public function getNextChapterId(ChapterId $chapterId): ?ChapterId { $currentChapter = $this->entityManager->getRepository(ChapterEntity::class)->findOneBy([ 'id' => $chapterId->getValue() ]); $qb = $this->entityManager->createQueryBuilder(); $qb->select('c') ->from(ChapterEntity::class, 'c') ->where('c.manga = :manga') ->andWhere('c.number > :number') ->andWhere('c.visible = true') ->andWhere('c.pagesDirectory IS NOT NULL OR c.cbzPath IS NOT NULL') ->orderBy('c.number', 'ASC') ->setMaxResults(1) ->setParameters([ 'manga' => $currentChapter->getManga(), 'number' => $currentChapter->getNumber() ]); $nextChapter = $qb->getQuery()->getOneOrNullResult(); return $nextChapter ? new ChapterId((string) $nextChapter->getId()) : null; } public function getPageContent(ChapterId $chapterId, PageNumber $pageNumber): PageContent { $chapter = $this->entityManager->getRepository(ChapterEntity::class)->findOneBy([ 'id' => $chapterId->getValue() ]); if (!$chapter) { throw ChapterNotFoundException::forChapter($chapterId); } $pagesDirectory = $chapter->getPagesDirectory(); if ($pagesDirectory && is_dir($pagesDirectory)) { return $this->getPageContentFromDirectory($chapterId, $pagesDirectory, $pageNumber); } $cbzPath = $chapter->getCbzPath(); if (!$cbzPath || !file_exists($cbzPath)) { throw ChapterNotFoundException::forChapter($chapterId); } return $this->getPageContentFromCbz($chapterId, $cbzPath, $pageNumber); } private function getImageFiles(string $pagesDirectory): array { $files = glob($pagesDirectory . '/*.{jpg,jpeg,png,webp,gif}', GLOB_BRACE) ?: []; sort($files); return $files; } private function getPagesFromDirectory(ChapterId $chapterId, string $pagesDirectory, int $page, int $itemsPerPage): array { $files = $this->getImageFiles($pagesDirectory); $start = ($page - 1) * $itemsPerPage; $end = min($start + $itemsPerPage, count($files)); $pages = []; for ($i = $start; $i < $end; $i++) { $imageContent = file_get_contents($files[$i]); if ($imageContent === false) { continue; } $imageSize = @getimagesizefromstring($imageContent); if ($imageSize === false) { continue; } $pages[] = new Page( basename($files[$i]), new PageNumber($i + 1), sprintf('/api/chapters/%s/pages/%d', $chapterId->getValue(), $i + 1), $imageSize[0], $imageSize[1] ); } return $pages; } private function getPagesFromCbz(ChapterId $chapterId, string $cbzPath, int $page, int $itemsPerPage): array { $zip = new ZipArchive(); $zip->open($cbzPath); $pages = []; $start = ($page - 1) * $itemsPerPage; $end = min($start + $itemsPerPage, $zip->numFiles); for ($i = $start; $i < $end; $i++) { $stat = $zip->statIndex($i); if ($stat === false) { continue; } $imageContent = $zip->getFromIndex($i); if ($imageContent === false) { continue; } $imageSize = @getimagesizefromstring($imageContent); if ($imageSize === false) { continue; } $pages[] = new Page( $stat['name'], new PageNumber($i + 1), sprintf('/api/chapters/%s/pages/%d', $chapterId->getValue(), $i + 1), $imageSize[0], $imageSize[1] ); } $zip->close(); return $pages; } private function getPageContentFromDirectory(ChapterId $chapterId, string $pagesDirectory, PageNumber $pageNumber): PageContent { $files = $this->getImageFiles($pagesDirectory); if (!$files || $pageNumber->getValue() > count($files)) { throw PageNotFoundException::forPage($chapterId, $pageNumber); } $filePath = $files[$pageNumber->getValue() - 1]; $imageContent = file_get_contents($filePath); if ($imageContent === false) { throw PageNotFoundException::forPage($chapterId, $pageNumber); } $imageSize = @getimagesizefromstring($imageContent); if ($imageSize === false) { throw PageNotFoundException::forPage($chapterId, $pageNumber); } $mimeType = $imageSize['mime'] ?? 'image/jpeg'; return new PageContent( basename($filePath), $pageNumber, base64_encode($imageContent), $mimeType, $imageSize[0], $imageSize[1] ); } private function getPageContentFromCbz(ChapterId $chapterId, string $cbzPath, PageNumber $pageNumber): PageContent { $zip = new ZipArchive(); $zip->open($cbzPath); if ($pageNumber->getValue() > $zip->numFiles) { $zip->close(); throw PageNotFoundException::forPage($chapterId, $pageNumber); } $index = $pageNumber->getValue() - 1; $stat = $zip->statIndex($index); if ($stat === false) { $zip->close(); throw PageNotFoundException::forPage($chapterId, $pageNumber); } $imageContent = $zip->getFromIndex($index); if ($imageContent === false) { $zip->close(); throw PageNotFoundException::forPage($chapterId, $pageNumber); } $imageSize = @getimagesizefromstring($imageContent); if ($imageSize === false) { $zip->close(); throw PageNotFoundException::forPage($chapterId, $pageNumber); } $mimeType = $imageSize['mime'] ?? 'image/jpeg'; $zip->close(); return new PageContent( $stat['name'], $pageNumber, base64_encode($imageContent), $mimeType, $imageSize[0], $imageSize[1] ); } }