Added:
- turbo + code adaptation - cover & thumbnails download
This commit is contained in:
@@ -89,6 +89,7 @@ class ActivityController extends AbstractController
|
||||
'manga' => $manga->getTitle(),
|
||||
'volume' => $chapter->getVolume(),
|
||||
'chapter' => $chapter->getNumber(),
|
||||
'chapterId' => $chapter->getId(),
|
||||
'title' => $chapter->getTitle(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -15,31 +15,42 @@ use App\Service\NotificationService;
|
||||
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\NonUniqueResultException;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Intervention\Image\Drivers\Gd\Driver;
|
||||
use Intervention\Image\ImageManager;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
use Symfony\Component\HttpFoundation\File\Exception\FileException;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Symfony\Component\Messenger\MessageBusInterface;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\String\Slugger\SluggerInterface;
|
||||
|
||||
class MangaController extends AbstractController
|
||||
{
|
||||
private ImageManager $imageManager;
|
||||
|
||||
public function __construct(
|
||||
private readonly string $projectDir,
|
||||
private readonly MangaRepository $mangaRepository,
|
||||
private readonly ChapterRepository $chapterRepository,
|
||||
private readonly MessageBusInterface $bus,
|
||||
private readonly CbzService $cbzService,
|
||||
private readonly ToolbarFactory $toolbarFactory,
|
||||
private readonly MangadexProvider $mangadexProvider,
|
||||
private readonly EntityManagerInterface $entityManager
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly NotificationService $notificationService,
|
||||
private readonly SluggerInterface $slugger
|
||||
)
|
||||
{
|
||||
$this->imageManager = new ImageManager(new Driver());
|
||||
}
|
||||
|
||||
#[Route('/manga', name: 'app_manga')]
|
||||
#[Route('/', name: 'app_manga')]
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$sort = $request->query->get('sort', 'title');
|
||||
@@ -57,11 +68,8 @@ class MangaController extends AbstractController
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NonUniqueResultException
|
||||
*/
|
||||
#[Route('/manga/chapters/{mangaSlug}', name: 'app_manga_show')]
|
||||
public function showChapters(string $mangaSlug): Response
|
||||
public function showChapters(string $mangaSlug, Request $request): Response
|
||||
{
|
||||
// $manga = $this->mangaRepository->findOneWithChapterBy(['slug' => $mangaSlug]);
|
||||
$manga = $this->mangaRepository->findOneBy(['slug' => $mangaSlug]);
|
||||
@@ -70,6 +78,16 @@ class MangaController extends AbstractController
|
||||
throw new NotFoundHttpException("Le manga demandé n'existe pas.");
|
||||
}
|
||||
|
||||
return $this->render('manga/show_chapters.html.twig', [
|
||||
'manga' => $manga,
|
||||
'toolbar' => $this->toolbarFactory->createToolbar('chapter_list', ['mangaId' => $manga->getId()])->getGroups(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
public function _chaptersByManga(int $id): Response
|
||||
{
|
||||
$manga = $this->mangaRepository->find($id);
|
||||
$chaptersByVolume = [];
|
||||
foreach ($manga->getChapters() as $chapter) {
|
||||
$volume = $chapter->getVolume() ?? 'Not Found';
|
||||
@@ -92,10 +110,10 @@ class MangaController extends AbstractController
|
||||
}
|
||||
return $b <=> $a;
|
||||
});
|
||||
return $this->render('manga/show_chapters.html.twig', [
|
||||
'chapters_by_volume' => $chaptersByVolume,
|
||||
|
||||
return $this->render('manga/_chapter_list.html.twig', [
|
||||
'manga' => $manga,
|
||||
'toolbar' => $this->toolbarFactory->createToolbar('chapter_list', ['mangaId' => $manga->getId()])->getGroups(),
|
||||
'chapters_by_volume' => $chaptersByVolume
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -154,11 +172,15 @@ class MangaController extends AbstractController
|
||||
#[Route('/addManga', name: 'app_manga_add')]
|
||||
public function addManga(Request $request): Response
|
||||
{
|
||||
$manga = $this->mangaRepository->findOneBy(['slug' => $request->request->get('slug')]);
|
||||
if ($manga) {
|
||||
return $this->redirectToRoute('app_manga_show', ['mangaSlug' => $manga->getSlug()]);
|
||||
}
|
||||
|
||||
$manga = new Manga();
|
||||
$manga->setTitle($request->request->get('title'))
|
||||
->setSlug($request->request->get('slug'))
|
||||
->setDescription($request->request->get('description'))
|
||||
->setImageUrl($request->request->get('imageUrl'))
|
||||
->setStatus($request->request->get('status'))
|
||||
->setGenres(explode(',', $request->request->get('genres')))
|
||||
->setAuthor($request->request->get('author'))
|
||||
@@ -166,6 +188,16 @@ class MangaController extends AbstractController
|
||||
->setRating($request->request->get('rating'))
|
||||
->setExternalId($request->request->get('externalId'));
|
||||
|
||||
// Traitement de l'image
|
||||
$imageUrl = $request->request->get('imageUrl');
|
||||
try {
|
||||
$imageUrls = $this->processAndSaveImage($imageUrl);
|
||||
$manga->setImageUrl($imageUrls['full']);
|
||||
$manga->setThumbnailUrl($imageUrls['thumbnail']);
|
||||
} catch (\Exception $e) {
|
||||
// Gérer l'exception (par exemple, logger l'erreur)
|
||||
}
|
||||
|
||||
$mergedChapters = $this->mangadexProvider->addAllChaptersToManga($manga);
|
||||
|
||||
if (empty($mergedChapters)) {
|
||||
@@ -189,7 +221,48 @@ class MangaController extends AbstractController
|
||||
return $this->redirectToRoute('app_manga_show', ['mangaSlug' => $manga->getSlug()]);
|
||||
}
|
||||
|
||||
#[Route('/addChapter/{id}', name: 'add_chapter')]
|
||||
/**
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
private function processAndSaveImage(string $imageUrl): array
|
||||
{
|
||||
$client = new Client();
|
||||
$response = $client->get($imageUrl);
|
||||
$tempImage = tmpfile();
|
||||
fwrite($tempImage, $response->getBody()->getContents());
|
||||
$tempImagePath = stream_get_meta_data($tempImage)['uri'];
|
||||
|
||||
// Générer un nom de fichier unique
|
||||
$originalFilename = pathinfo($imageUrl, PATHINFO_FILENAME);
|
||||
$safeFilename = $this->slugger->slug($originalFilename);
|
||||
$newFilename = $safeFilename . '-' . uniqid() . '.' . 'jpg';
|
||||
|
||||
try {
|
||||
// Créer et sauvegarder la miniature
|
||||
$thumbnail = $this->imageManager->read($tempImagePath);
|
||||
$thumbnail->cover(300, 440);
|
||||
$thumbnail->save($this->projectDir . '/public/images/thumbnails/' . $newFilename, quality: 85);
|
||||
|
||||
// Sauvegarder l'image en taille réelle
|
||||
$fullImage = $this->imageManager->read($tempImagePath);
|
||||
$fullImage->save($this->projectDir . '/public/images/full/' . $newFilename, quality: 90);
|
||||
|
||||
// Fermer et supprimer le fichier temporaire
|
||||
fclose($tempImage);
|
||||
|
||||
return [
|
||||
'full' => '/images/full/' . $newFilename,
|
||||
'thumbnail' => '/images/thumbnails/' . $newFilename
|
||||
];
|
||||
|
||||
} catch (FileException $e) {
|
||||
// Fermer le fichier temporaire en cas d'erreur
|
||||
fclose($tempImage);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
#[Route('/searchChapter/{id}', name: 'search_chapter')]
|
||||
public function addChapterMessenger(int $id): JsonResponse
|
||||
{
|
||||
$chapter = $this->chapterRepository->find($id);
|
||||
@@ -204,36 +277,85 @@ class MangaController extends AbstractController
|
||||
return new JsonResponse(['success' => 'Scrapping started...'], 200);
|
||||
}
|
||||
|
||||
#[Route('/searchVolume/{mangaSlug}/{volume}', name: 'search_volume')]
|
||||
public function searchVolume(string $mangaSlug, int $volume): JsonResponse
|
||||
{
|
||||
$manga = $this->mangaRepository->findOneBy(['slug' => $mangaSlug]);
|
||||
if (!$manga) {
|
||||
return new JsonResponse(['error' => 'Manga Not Found.'], 400);
|
||||
}
|
||||
|
||||
$volumeChapters = $this->chapterRepository->findBy([
|
||||
'manga' => $manga,
|
||||
'volume' => $volume
|
||||
]);
|
||||
|
||||
if (empty($volumeChapters)) {
|
||||
$this->notificationService->sendUpdate(['error' => 'No chapters found for this volume.']);
|
||||
return new JsonResponse(['error' => 'No chapters found for this volume.'], 200);
|
||||
}
|
||||
|
||||
foreach ($volumeChapters as $chapter) {
|
||||
if ($chapter->getCbzPath() === null) {
|
||||
$this->bus->dispatch(new DownloadChapter($chapter->getId()));
|
||||
}
|
||||
}
|
||||
|
||||
return new JsonResponse(['success' => 'Scrapping started...'], 200);
|
||||
}
|
||||
|
||||
#[Route('/download-cbz/{chapterId}', name: 'download_cbz')]
|
||||
public function downloadChapter(int $chapterId): BinaryFileResponse
|
||||
public function downloadChapter(int $chapterId): BinaryFileResponse|JsonResponse
|
||||
{
|
||||
$chapter = $this->chapterRepository->find($chapterId);
|
||||
if (!$chapter) {
|
||||
throw $this->createNotFoundException("Le chapitre demandé n'existe pas.");
|
||||
$this->notificationService->sendUpdate(['error' => 'Chapitre non trouvé.']);
|
||||
return new JsonResponse(['error' => 'Chapitre non trouvé.'], 200);
|
||||
}
|
||||
|
||||
$cbzPath = $chapter->getCbzPath();
|
||||
if (!$cbzPath || !file_exists($cbzPath)) {
|
||||
throw $this->createNotFoundException("Le fichier CBZ n'existe pas.");
|
||||
$this->notificationService->sendUpdate(['error' => 'Le fichier CBZ n\'existe pas.']);
|
||||
return new JsonResponse(['error' => 'Le fichier CBZ n\'existe pas.'], 200);
|
||||
}
|
||||
|
||||
$response = new BinaryFileResponse($cbzPath);
|
||||
|
||||
// Vérifier si c'est un volume complet ou un chapitre individuel
|
||||
$isFullVolume = $this->isFullVolume($chapter);
|
||||
$fileName = $isFullVolume
|
||||
? $this->cbzService->generateFileName($chapter->getManga(), $chapter->getVolume())
|
||||
: $this->cbzService->generateFileName($chapter->getManga(), null, $chapter->getNumber());
|
||||
|
||||
if ($isFullVolume) {
|
||||
$fileName = sprintf("%s_volume_%02d.cbz", $chapter->getManga()->getSlug(), $chapter->getVolume());
|
||||
} else {
|
||||
$fileName = sprintf("%s_chapter_%s.cbz", $chapter->getManga()->getSlug(), number_format($chapter->getNumber(), 2));
|
||||
return $this->cbzService->createBinaryFileResponse($cbzPath, $fileName);
|
||||
}
|
||||
|
||||
#[Route('/download-volume/{mangaSlug}/{volume}', name: 'download_volume')]
|
||||
public function downloadVolume(string $mangaSlug, int $volume): BinaryFileResponse|JsonResponse
|
||||
{
|
||||
$manga = $this->mangaRepository->findOneBy(['slug' => $mangaSlug]);
|
||||
|
||||
$volumeChapters = $this->chapterRepository->findBy([
|
||||
'manga' => $manga,
|
||||
'volume' => $volume
|
||||
]);
|
||||
|
||||
if (empty($volumeChapters)) {
|
||||
$this->notificationService->sendUpdate(['error' => 'Aucun chapitre trouvé pour ce volume.']);
|
||||
}
|
||||
|
||||
$response->setContentDisposition(
|
||||
ResponseHeaderBag::DISPOSITION_ATTACHMENT,
|
||||
$fileName
|
||||
);
|
||||
if (!$this->cbzService->doAllChaptersHaveCbz($volumeChapters)) {
|
||||
$this->notificationService->sendUpdate(['error' => 'Tous les chapitres du volume ne sont pas scrapés.']);
|
||||
return new JsonResponse(['error' => 'Tous les chapitres du volume ne sont pas scrapés.'], 200);
|
||||
}
|
||||
|
||||
return $response;
|
||||
$fileName = $this->cbzService->generateFileName($manga, $volume);
|
||||
|
||||
if ($this->cbzService->areAllChaptersCbzIdentical($volumeChapters)) {
|
||||
return $this->cbzService->createBinaryFileResponse($volumeChapters[0]->getCbzPath(), $fileName);
|
||||
} else {
|
||||
$tempFile = $this->cbzService->createVolumeArchive($volumeChapters);
|
||||
$response = $this->cbzService->createBinaryFileResponse($tempFile, $fileName);
|
||||
$response->deleteFileAfterSend(true);
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
||||
#[Route('/refresh_metadata', name: 'refresh_metadata')]
|
||||
|
||||
@@ -8,83 +8,94 @@ use App\Entity\Manga;
|
||||
use App\Message\DownloadChapter;
|
||||
use App\Repository\ChapterRepository;
|
||||
use App\Repository\MangaRepository;
|
||||
use App\Service\ActivityService;
|
||||
use App\Service\MangadexProvider;
|
||||
use App\Service\MangaScraperService;
|
||||
use App\Service\MangaUpdatesMetadataProvider;
|
||||
use App\Service\SushiScanProviderService;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Intervention\Image\Drivers\Gd\Driver;
|
||||
use Intervention\Image\ImageManager;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\File\Exception\FileException;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Messenger\Envelope;
|
||||
use Symfony\Component\Messenger\MessageBusInterface;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\Serializer\SerializerInterface;
|
||||
use Symfony\Component\String\Slugger\SluggerInterface;
|
||||
|
||||
class TestController extends AbstractController
|
||||
{
|
||||
private ImageManager $imageManager;
|
||||
public function __construct(
|
||||
private MangadexProvider $mangadexProvider,
|
||||
private MangaRepository $mangaRepository,
|
||||
private MessageBusInterface $bus,
|
||||
private Connection $connection,
|
||||
private SerializerInterface $serializer,
|
||||
private readonly ChapterRepository $chapterRepository
|
||||
private string $projectDir,
|
||||
private SluggerInterface $slugger,
|
||||
private MangaRepository $mangaRepository
|
||||
)
|
||||
{
|
||||
$this->imageManager = new ImageManager(new Driver());
|
||||
}
|
||||
|
||||
#[Route('/test', name: 'test')]
|
||||
public function test(): Response
|
||||
{
|
||||
$sqlPending = 'SELECT * FROM messenger_messages WHERE queue_name = :queue';
|
||||
$pending = $this->connection->fetchAllAssociative($sqlPending, ['queue' => 'default']);
|
||||
$mangas = $this->mangaRepository->findAll();
|
||||
|
||||
// // Requête pour récupérer les messages en cours de traitement
|
||||
// $sqlProcessing = 'SELECT * FROM messenger_messages WHERE queue_name = :queue AND available_at IS NOT NULL';
|
||||
// $processing = $this->connection->fetchAllAssociative($sqlProcessing, ['queue' => 'default']);
|
||||
|
||||
// dd($pending);
|
||||
$decoded = $this->decodeMessages($pending);
|
||||
|
||||
$status = [];
|
||||
foreach($decoded as $message) {
|
||||
$message = $message['body'];
|
||||
if($message instanceof Envelope) {
|
||||
$chapter = $this->chapterRepository->find($message->getMessage()->getChapterId());
|
||||
$manga = $chapter->getManga();
|
||||
$status[] = [
|
||||
'manga' => $manga->getTitle(),
|
||||
'volume' => $chapter->getVolume(),
|
||||
'chapter' => $chapter->getNumber(),
|
||||
'title' => $chapter->getTitle(),
|
||||
];
|
||||
$changed = 0;
|
||||
foreach ($mangas as $manga){
|
||||
//si getImageUrl() retourne un lien sous la forme d'une URL (https ou http)
|
||||
if($manga->getImageUrl()){
|
||||
$imageUrls = $this->processAndSaveImage($manga->getImageUrl());
|
||||
$manga->setThumbnailUrl($imageUrls['thumbnail']);
|
||||
$this->mangaRepository->save($manga, true);
|
||||
$changed++;
|
||||
}
|
||||
}
|
||||
|
||||
// $this->bus->dispatch(new DownloadChapter(1));
|
||||
|
||||
dd($status);
|
||||
return new JsonResponse(['changed' => $changed]);
|
||||
}
|
||||
|
||||
private function decodeMessages(array $messages): array
|
||||
/**
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
private function processAndSaveImage(string $imageUrl): array
|
||||
{
|
||||
$decodedMessages = [];
|
||||
$image = file_get_contents($this->projectDir . '/public' .$imageUrl);
|
||||
$tempImage = tmpfile();
|
||||
fwrite($tempImage, $image);
|
||||
$tempImagePath = stream_get_meta_data($tempImage)['uri'];
|
||||
|
||||
foreach ($messages as $message) {
|
||||
$decodedMessages[] = [
|
||||
'id' => $message['id'],
|
||||
'body' => $this->decodeMessageBody($message['body']),
|
||||
'headers' => json_decode($message['headers'], true),
|
||||
// Générer un nom de fichier unique
|
||||
$originalFilename = pathinfo($imageUrl, PATHINFO_FILENAME);
|
||||
$safeFilename = $this->slugger->slug($originalFilename);
|
||||
$newFilename = $safeFilename . '-' . uniqid() . '.' . 'jpg';
|
||||
|
||||
try {
|
||||
// Créer et sauvegarder la miniature
|
||||
$thumbnail = $this->imageManager->read($tempImagePath);
|
||||
$thumbnail->cover(300, 440);
|
||||
$thumbnail->save($this->projectDir . '/public/images/thumbnails/' . $newFilename, quality: 85);
|
||||
|
||||
// Sauvegarder l'image en taille réelle
|
||||
// $fullImage = $this->imageManager->read($tempImagePath);
|
||||
// $fullImage->save($this->projectDir . '/public/images/full/' . $newFilename, quality: 90);
|
||||
|
||||
// Fermer et supprimer le fichier temporaire
|
||||
fclose($tempImage);
|
||||
|
||||
return [
|
||||
'full' => '/images/full/' . $newFilename,
|
||||
'thumbnail' => '/images/thumbnails/' . $newFilename
|
||||
];
|
||||
|
||||
} catch (FileException $e) {
|
||||
// Fermer le fichier temporaire en cas d'erreur
|
||||
fclose($tempImage);
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return $decodedMessages;
|
||||
}
|
||||
|
||||
private function decodeMessageBody(string $body)
|
||||
{
|
||||
return unserialize(stripcslashes($body));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -16,10 +16,10 @@ class ContentSource
|
||||
#[ORM\Column(length: 255)]
|
||||
private ?string $baseUrl = null;
|
||||
|
||||
#[ORM\Column(length: 255)]
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
private ?string $imageSelector = null;
|
||||
|
||||
#[ORM\Column(length: 255)]
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
private ?string $NextPageSelector = null;
|
||||
|
||||
#[ORM\Column(length: 255)]
|
||||
|
||||
@@ -52,6 +52,9 @@ class Manga
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
private ?string $status = null;
|
||||
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
private ?string $thumbnailUrl = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->chapters = new ArrayCollection();
|
||||
@@ -234,4 +237,16 @@ class Manga
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getThumbnailUrl(): ?string
|
||||
{
|
||||
return $this->thumbnailUrl;
|
||||
}
|
||||
|
||||
public function setThumbnailUrl(?string $thumbnailUrl): static
|
||||
{
|
||||
$this->thumbnailUrl = $thumbnailUrl;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\EventListener;
|
||||
|
||||
use App\Entity\Chapter;
|
||||
use App\Entity\Manga;
|
||||
use App\Entity\Page;
|
||||
use App\EventSubscriber\MangaScrapedEvent;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
class MangaScrapedListener
|
||||
{
|
||||
private EntityManagerInterface $entityManager;
|
||||
public function __construct(EntityManagerInterface $entityManager)
|
||||
{
|
||||
$this->entityManager = $entityManager;
|
||||
}
|
||||
|
||||
public function onMangaScraped(MangaScrapedEvent $event): void
|
||||
{
|
||||
$mangaData = $event->getMangaData();
|
||||
$manga = $this->entityManager->getRepository(Manga::class)->findOneBy(['title' => $mangaData['title']]);
|
||||
if (!$manga) {
|
||||
$manga = new Manga();
|
||||
$manga->setTitle($mangaData['title']);
|
||||
$this->entityManager->persist($manga);
|
||||
}
|
||||
|
||||
$chapter = $manga->getChapterByNumber($mangaData['chapter']);
|
||||
if (!$chapter) {
|
||||
$chapter = (new Chapter())
|
||||
->setNumber($mangaData['chapter']);
|
||||
$manga->addChapter($chapter);
|
||||
$this->entityManager->persist($chapter);
|
||||
$this->entityManager->persist($manga);
|
||||
}
|
||||
|
||||
$chapter->setLocalPath($mangaData['directory']);
|
||||
|
||||
foreach ($mangaData['pages'] as $pageData) {
|
||||
$page = $chapter->getPageByNumber($pageData['page_number']);
|
||||
if (!$page) {
|
||||
$page = (new Page())
|
||||
->setNumber($pageData['page_number'])
|
||||
->setImageUrl($pageData['image_url'])
|
||||
->setImageLocalUrl($pageData['local_image_url']);
|
||||
|
||||
$chapter->addPagesLink($page);
|
||||
$this->entityManager->persist($chapter);
|
||||
$this->entityManager->persist($page);
|
||||
}
|
||||
}
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\EventSubscriber;
|
||||
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
class MangaScrapedEvent extends Event
|
||||
{
|
||||
public const NAME = 'manga.scraped';
|
||||
|
||||
private string $mangaTitle;
|
||||
private float $chapterNumber;
|
||||
private array $pagesData;
|
||||
private string $chapterDirectory;
|
||||
|
||||
public function __construct(string $mangaTitle, float $chapterNumber, array $pagesData, string $chapterDirectory)
|
||||
{
|
||||
$this->mangaTitle = $mangaTitle;
|
||||
$this->chapterNumber = $chapterNumber;
|
||||
$this->pagesData = $pagesData;
|
||||
$this->chapterDirectory = $chapterDirectory;
|
||||
}
|
||||
|
||||
public function getMangaData(): array
|
||||
{
|
||||
return [
|
||||
'title' => $this->mangaTitle,
|
||||
'chapter' => $this->chapterNumber,
|
||||
'pages' => $this->pagesData,
|
||||
'directory' => $this->chapterDirectory
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,10 @@
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use App\Entity\Manga;
|
||||
use Exception;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
|
||||
use Symfony\Component\String\Slugger\SluggerInterface;
|
||||
use ZipArchive;
|
||||
|
||||
@@ -133,4 +136,68 @@ class CbzService
|
||||
sort($images);
|
||||
return $images;
|
||||
}
|
||||
|
||||
public function createVolumeArchive(array $chapters): string
|
||||
{
|
||||
$tempFile = tempnam(sys_get_temp_dir(), 'volume_cbz_');
|
||||
$zip = new ZipArchive();
|
||||
if ($zip->open($tempFile, ZipArchive::CREATE) !== TRUE) {
|
||||
throw new \RuntimeException("Impossible de créer le fichier ZIP temporaire.");
|
||||
}
|
||||
|
||||
foreach ($chapters as $chapter) {
|
||||
$chapterZip = new ZipArchive();
|
||||
if ($chapterZip->open($chapter->getCbzPath()) === TRUE) {
|
||||
for ($i = 0; $i < $chapterZip->numFiles; $i++) {
|
||||
$filename = $chapterZip->getNameIndex($i);
|
||||
$fileContent = $chapterZip->getFromIndex($i);
|
||||
$zip->addFromString("Chapter " . $chapter->getNumber() . "/" . $filename, $fileContent);
|
||||
}
|
||||
$chapterZip->close();
|
||||
}
|
||||
}
|
||||
|
||||
$zip->close();
|
||||
return $tempFile;
|
||||
}
|
||||
|
||||
public function generateFileName(Manga $manga, ?int $volume = null, ?float $chapterNumber = null): string
|
||||
{
|
||||
$sluggedTitle = $this->slugger->slug($manga->getTitle())->lower();
|
||||
if ($volume !== null) {
|
||||
return sprintf("%s_volume_%02d.cbz", $sluggedTitle, $volume);
|
||||
} elseif ($chapterNumber !== null) {
|
||||
return sprintf("%s_chapter_%s.cbz", $sluggedTitle, number_format($chapterNumber, 2));
|
||||
} else {
|
||||
throw new \InvalidArgumentException("Either volume or chapter number must be provided");
|
||||
}
|
||||
}
|
||||
|
||||
public function createBinaryFileResponse(string $filePath, string $fileName): BinaryFileResponse
|
||||
{
|
||||
$response = new BinaryFileResponse($filePath);
|
||||
$response->setContentDisposition(
|
||||
ResponseHeaderBag::DISPOSITION_ATTACHMENT,
|
||||
$fileName
|
||||
);
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function areAllChaptersCbzIdentical(array $chapters): bool
|
||||
{
|
||||
if (empty($chapters)) {
|
||||
return false;
|
||||
}
|
||||
$firstCbzPath = $chapters[0]->getCbzPath();
|
||||
return array_reduce($chapters, function ($carry, $chapter) use ($firstCbzPath) {
|
||||
return $carry && $chapter->getCbzPath() === $firstCbzPath;
|
||||
}, true);
|
||||
}
|
||||
|
||||
public function doAllChaptersHaveCbz(array $chapters): bool
|
||||
{
|
||||
return array_reduce($chapters, function ($carry, $chapter) {
|
||||
return $carry && $chapter->getCbzPath() !== null;
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ use App\Entity\Chapter;
|
||||
use App\Entity\Manga;
|
||||
use App\Entity\ContentSource;
|
||||
use App\Event\PageScrappingProgressEvent;
|
||||
use App\EventSubscriber\MangaScrapedEvent;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Exception;
|
||||
use GuzzleHttp\Client;
|
||||
|
||||
Reference in New Issue
Block a user