From 7eba0981c8bd639fe2b6bcfe3f93c6fcbe4851c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Guillot?= Date: Wed, 24 Jul 2024 17:21:44 +0200 Subject: [PATCH] Added: - FileSystemManager.php refactoring of all write/read actions on filesystem Deleted: - old ToolbarManager.php --- config/services.yaml | 20 +---- src/Controller/ImportController.php | 29 ++++---- src/Controller/MangaController.php | 51 ++----------- src/Manager/FileSystemManager.php | 89 +++++++++++++++++++++++ src/Manager/ToolbarManager.php | 42 ----------- src/Service/MangaImportService.php | 46 ++++-------- src/Service/Scraper/AbstractScraper.php | 30 ++------ src/Service/Scraper/HtmlScraper.php | 15 +--- src/Service/Scraper/JavascriptScraper.php | 2 +- src/Service/Scraper/MangadexScraper.php | 15 +--- 10 files changed, 142 insertions(+), 197 deletions(-) create mode 100644 src/Manager/FileSystemManager.php delete mode 100644 src/Manager/ToolbarManager.php diff --git a/config/services.yaml b/config/services.yaml index adce1b7..5741937 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -45,23 +45,15 @@ services: arguments: $projectDir: '%kernel.project_dir%' - App\Service\MangaImportService: - arguments: - $projectDir: '%kernel.project_dir%' - - App\Controller\ImportController: - arguments: - $projectDir: '%kernel.project_dir%' - App\Controller\TestController: arguments: $projectDir: '%kernel.project_dir%' - App\Controller\MangaController: + App\Service\CbrToCbzConverter: arguments: $projectDir: '%kernel.project_dir%' - - App\Service\CbrToCbzConverter: + + App\Manager\FileSystemManager: arguments: $projectDir: '%kernel.project_dir%' @@ -83,18 +75,12 @@ services: # Scrapers App\Service\Scraper\HtmlScraper: - arguments: - $projectDir: '%kernel.project_dir%' tags: [ 'app.scraper' ] App\Service\Scraper\JavascriptScraper: - arguments: - $projectDir: '%kernel.project_dir%' tags: [ 'app.scraper' ] App\Service\Scraper\MangadexScraper: - arguments: - $projectDir: '%kernel.project_dir%' tags: [ 'app.scraper' ] # Scraper Factory diff --git a/src/Controller/ImportController.php b/src/Controller/ImportController.php index db4a6ce..927e626 100644 --- a/src/Controller/ImportController.php +++ b/src/Controller/ImportController.php @@ -2,6 +2,7 @@ namespace App\Controller; +use App\Manager\FileSystemManager; use App\Repository\ChapterRepository; use App\Repository\MangaRepository; use App\Service\CbrToCbzConverter; @@ -19,10 +20,8 @@ use Symfony\Component\String\Slugger\SluggerInterface; class ImportController extends AbstractController { - private const string UPLOADS_DIRECTORY = 'public/uploads'; - public function __construct( - private readonly string $projectDir, + private readonly FileSystemManager $fileSystemManager, private readonly CbzService $cbzService, private readonly MangaImportService $mangaImportService, private readonly NotificationService $notificationService, @@ -43,13 +42,16 @@ class ImportController extends AbstractController foreach ($files as $file) { if ($file && in_array($file->getClientOriginalExtension(), ['cbz', 'cbr'])) { $originalFileName = $file->getClientOriginalName(); - $filename = uniqid() . '.' . $file->getClientOriginalExtension(); try { - $file->move($this->projectDir . '/' . self::UPLOADS_DIRECTORY, $filename); + $tmpPath = $this->fileSystemManager->moveUploadedFile( + $file->getPathname(), + $this->fileSystemManager->getUploadsDirectory(), + $file->getClientOriginalName() + ); $importFiles[] = [ 'id' => uniqid(), - 'path' => $this->projectDir . '/' . self::UPLOADS_DIRECTORY . '/' . $filename, + 'path' => $tmpPath, 'original_name' => $originalFileName, ]; } catch (FileException $e) { @@ -85,7 +87,7 @@ class ImportController extends AbstractController * @throws Exception */ #[Route('/import/match', name: 'import_match')] - public function match(Request $request, SessionInterface $session): Response + public function match(SessionInterface $session): Response { $files = $session->get('import_files', []); if (empty($files)) { @@ -137,7 +139,8 @@ class ImportController extends AbstractController ]); } - private function formatBytes($bytes, $precision = 2) { + private function formatBytes($bytes, $precision = 2) + { $units = ['B', 'KB', 'MB', 'GB', 'TB']; $bytes = max($bytes, 0); $pow = floor(($bytes ? log($bytes) : 0) / log(1024)); @@ -174,7 +177,7 @@ class ImportController extends AbstractController throw new \Exception('Manga non trouvé.'); } - if(!is_null($chapter)){ + if (!is_null($chapter)) { $chapter = $manga->getChapterByNumber($chapter); if (!$chapter) { throw new \Exception('Chapitre non trouvé.'); @@ -182,17 +185,15 @@ class ImportController extends AbstractController } $importedFiles[] = $file['original_name']; - $this->mangaImportService->importFile($manga, $volume, $chapter,$file['path']); + $this->mangaImportService->importFile($manga, $volume, $chapter, $file['path']); } catch (\Exception $e) { $errors[] = "Erreur lors de l'import de {$file['original_name']} : " . $e->getMessage(); } } // Nettoyer les fichiers temporaires non importés - foreach ($files as $fileId => $file) { - if (!in_array($fileId, (array)$selectedFiles) && file_exists($file['path'])) { - unlink($file['path']); - } + foreach ($files as $file) { + $this->fileSystemManager->deleteFile($file['path']); } // Nettoyer la session diff --git a/src/Controller/MangaController.php b/src/Controller/MangaController.php index 4a788d9..29fbfe0 100644 --- a/src/Controller/MangaController.php +++ b/src/Controller/MangaController.php @@ -5,6 +5,7 @@ namespace App\Controller; use App\Entity\Chapter; use App\Entity\Manga; use App\Form\MangaEditType; +use App\Manager\FileSystemManager; use App\Manager\Toolbar\Factory\ToolbarFactory; use App\Message\DownloadChapter; use App\Message\RefreshMetadata; @@ -38,7 +39,7 @@ class MangaController extends AbstractController private ImageManager $imageManager; public function __construct( - private readonly string $projectDir, + private readonly FileSystemManager $fileSystemManager, private readonly MangaRepository $mangaRepository, private readonly ChapterRepository $chapterRepository, private readonly MessageBusInterface $bus, @@ -47,7 +48,6 @@ class MangaController extends AbstractController private readonly MangadexProvider $mangadexProvider, private readonly EntityManagerInterface $entityManager, private readonly NotificationService $notificationService, - private readonly SluggerInterface $slugger, private readonly ContentSourceRepository $contentSourceRepository ) { @@ -213,39 +213,6 @@ class MangaController extends AbstractController return new JsonResponse(['success' => 'Chapter hidden.'], 200); } -// #[Route('/manga/read/{mangaSlug}/{chapterNumber}/{pageNumber}', name: 'app_manga_read')] -// public function readChapterPage(string $mangaSlug, float $chapterNumber, int $pageNumber = 1): Response -// { -// $manga = $this->mangaRepository->findOneBy(['slug' => $mangaSlug]); -// if (!$manga) { -// throw $this->createNotFoundException("Le manga demandé n'existe pas."); -// } -// -// $chapter = $manga->getChapterByNumber($chapterNumber); -// if (!$chapter) { -// throw $this->createNotFoundException("Le chapitre demandé n'existe pas."); -// } -// -// if (is_null($chapter->getCbzPath())) { -// throw $this->createNotFoundException("Le chapitre demandé n'a pas été scrapé."); -// } -// -// $pageContent = $this->cbzService->getPageContent($chapter->getCbzPath(), $pageNumber); -// if (!$pageContent) { -// throw $this->createNotFoundException("La page demandée n'existe pas."); -// } -// -// $totalPages = $this->cbzService->getPageCount($chapter->getCbzPath()); -// -// return $this->render('manga/manga_reader.html.twig', [ -// 'manga' => $manga, -// 'chapter' => $chapter, -// 'currentPage' => $pageNumber, -// 'totalPages' => $totalPages, -// 'pageContent' => base64_encode($pageContent), -// ]); -// } - #[Route('/manga/search/{query}', name: 'app_manga_search')] public function search(string $query = ''): Response { @@ -273,8 +240,7 @@ class MangaController extends AbstractController ->setPublicationYear($request->request->get('publicationYear')) ->setRating($request->request->get('rating')) ->setExternalId($request->request->get('externalId')) - ->setMonitored(false) - ; + ->setMonitored(false); // Traitement de l'image $imageUrl = $request->request->get('imageUrl'); @@ -322,25 +288,24 @@ class MangaController extends AbstractController // Générer un nom de fichier unique $originalFilename = pathinfo($imageUrl, PATHINFO_FILENAME); - $safeFilename = $this->slugger->slug($originalFilename); - $newFilename = $safeFilename . '-' . uniqid() . '.' . 'jpg'; + $newFilename = $this->fileSystemManager->generateUniqueImageFilename($imageUrl); 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); + $thumbnail->save($this->fileSystemManager->getImagePath('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); + $fullImage->save($this->fileSystemManager->getImagePath('full') . '/' . $newFilename, quality: 90); // Fermer et supprimer le fichier temporaire fclose($tempImage); return [ - 'full' => '/images/full/' . $newFilename, - 'thumbnail' => '/images/thumbnails/' . $newFilename + 'full' => $this->fileSystemManager->getImagePath('full') . '/' . $newFilename, + 'thumbnail' => $this->fileSystemManager->getImagePath('thumbnails') . '/' . $newFilename ]; } catch (FileException $e) { diff --git a/src/Manager/FileSystemManager.php b/src/Manager/FileSystemManager.php new file mode 100644 index 0000000..4cf757e --- /dev/null +++ b/src/Manager/FileSystemManager.php @@ -0,0 +1,89 @@ +projectDir . '/' . self::CBZ_DIRECTORY . '/' . ucfirst($mangaSlug) . " ($year)"; + $this->filesystem->mkdir($directoryPath, 0755); + return $directoryPath; + } + + public function createVolumeDirectory(string $mangaDir, int $volume): string + { + $volumeDir = sprintf('%s/volume_%02d', $mangaDir, $volume); + $this->filesystem->mkdir($volumeDir, 0755); + return $volumeDir; + } + + public function moveUploadedFile(string $sourcePath, string $destinationDir, string $originalFilename): string + { + $newFilename = $this->generateUniqueFilename($originalFilename); + $destinationPath = $destinationDir . '/' . $newFilename; + $this->filesystem->rename($sourcePath, $destinationPath, true); + return $destinationPath; + } + + public function deleteFile(string $filePath): void + { + if ($this->filesystem->exists($filePath)) { + $this->filesystem->remove($filePath); + } + } + + public function deleteDirectory(string $directoryPath): void + { + if ($this->filesystem->exists($directoryPath)) { + $this->filesystem->remove($directoryPath); + } + } + + public function fileExists(string $filePath): bool + { + return $this->filesystem->exists($filePath); + } + + public function moveFile(string $sourcePath, string $destinationPath): void + { + $this->filesystem->rename($sourcePath, $destinationPath, true); + } + + public function getUploadsDirectory(): string + { + return $this->projectDir . '/' . self::UPLOADS_DIRECTORY; + } + + private function generateUniqueFilename(string $originalFilename): string + { + $safeFilename = $this->slugger->slug(pathinfo($originalFilename, PATHINFO_FILENAME)); + return $safeFilename . '-' . uniqid() . '.' . pathinfo($originalFilename, PATHINFO_EXTENSION); + } + + public function getImagePath(string $subDir = ''): string + { + return $this->projectDir . '/' . self::IMAGES_DIRECTORY . ($subDir ? "/$subDir" : ''); + } + + public function generateUniqueImageFilename(string $originalFilename): string + { + $safeFilename = $this->slugger->slug(pathinfo($originalFilename, PATHINFO_FILENAME)); + return $safeFilename . '-' . uniqid() . '.jpg'; + } +} diff --git a/src/Manager/ToolbarManager.php b/src/Manager/ToolbarManager.php deleted file mode 100644 index 4c2ff30..0000000 --- a/src/Manager/ToolbarManager.php +++ /dev/null @@ -1,42 +0,0 @@ - $this->getSortItems(), - 'filterItems' => $this->getFilterItems(), - 'viewOptions' => $this->getViewOptions() - ]; - } - - private function getSortItems(): array - { - return [ - ['text' => 'Par titre', 'action' => 'sort', 'data' => ['sort-option' => 'title']], - ['text' => 'Par année de publication', 'action' => 'sort', 'data' => ['sort-option' => 'publicationYear']], - ['text' => 'Par date d\'ajout', 'action' => 'sort', 'data' => ['sort-option' => 'createdAt']] - ]; - } - - private function getFilterItems(): array - { - return [ - ['text' => 'Tous les mangas', 'action' => 'filter', 'data' => ['filter-option' => 'all']], - ['text' => 'Mangas en cours', 'action' => 'filter', 'data' => ['filter-option' => 'ongoing']], - ['text' => 'Mangas terminés', 'action' => 'filter', 'data' => ['filter-option' => 'completed']] - ]; - } - - private function getViewOptions(): array - { - return [ - ['text' => 'Vue poster', 'action' => 'changeView', 'data' => ['view-option' => 'poster']], - ['text' => 'Vue résumé', 'action' => 'changeView', 'data' => ['view-option' => 'resume']], - ['text' => 'Vue table', 'action' => 'changeView', 'data' => ['view-option' => 'table']] - ]; - } -} diff --git a/src/Service/MangaImportService.php b/src/Service/MangaImportService.php index db3207d..0556701 100644 --- a/src/Service/MangaImportService.php +++ b/src/Service/MangaImportService.php @@ -4,23 +4,19 @@ namespace App\Service; use App\Entity\Chapter; use App\Entity\Manga; +use App\Manager\FileSystemManager; use App\Repository\ChapterRepository; use Doctrine\ORM\EntityManagerInterface; use Exception; -use JetBrains\PhpStorm\NoReturn; -use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\String\Slugger\SluggerInterface; -class MangaImportService +readonly class MangaImportService { - private const string CBZ_DIRECTORY = 'public/cbz'; - public function __construct( - private readonly string $projectDir, - private readonly EntityManagerInterface $entityManager, - private readonly ChapterRepository $chapterRepository, - private readonly Filesystem $filesystem, - private readonly SluggerInterface $slugger + private FileSystemManager $fileSystemManager, + private EntityManagerInterface $entityManager, + private ChapterRepository $chapterRepository, + private SluggerInterface $slugger ) { } @@ -45,18 +41,17 @@ class MangaImportService private function importVolume(Manga $manga, int $volume, string $tempFilePath): void { $permanentFileName = $this->createPermanentFileName($manga, $volume); - $mangaDirectory = $this->createMangaDirectory($manga); - $permanentFilePath = $this->projectDir . '/' . $mangaDirectory .'/volume_' . sprintf('%02d', $volume) . '/' . $permanentFileName; + $mangaDirectory = $this->fileSystemManager->createMangaDirectory($manga->getSlug(), $manga->getPublicationYear()); + $volumeDirectory = $this->fileSystemManager->createVolumeDirectory($mangaDirectory, $volume); + $permanentFilePath = $volumeDirectory . '/' . $permanentFileName; - if ($this->filesystem->exists($permanentFilePath)) { + if ($this->fileSystemManager->fileExists($permanentFilePath)) { throw new \RuntimeException("Un fichier pour ce volume existe déjà."); } - $this->filesystem->mkdir(dirname($permanentFilePath), 0755); - $this->filesystem->rename($tempFilePath, $permanentFilePath, true); + $this->fileSystemManager->moveFile($tempFilePath, $permanentFilePath); $this->updateVolumeChapters($manga, $volume, $permanentFilePath); - $this->entityManager->flush(); } @@ -67,15 +62,15 @@ class MangaImportService { $volume = $chapter->getVolume(); $permanentFileName = $this->createPermanentFileName($manga, $volume, $chapter->getNumber()); - $mangaDirectory = $this->createMangaDirectory($manga); - $permanentFilePath = $this->projectDir . '/' . $mangaDirectory .'/volume_' . sprintf('%02d', $volume) . '/' . $permanentFileName; + $mangaDirectory = $this->fileSystemManager->createMangaDirectory($manga->getSlug(), $manga->getPublicationYear()); + $volumeDirectory = $this->fileSystemManager->createVolumeDirectory($mangaDirectory, $chapter->getVolume()); + $permanentFilePath = $volumeDirectory . '/' . $permanentFileName; - if ($this->filesystem->exists($permanentFilePath)) { + if ($this->fileSystemManager->fileExists($permanentFilePath)) { throw new \RuntimeException("Un fichier pour ce chapitre existe déjà."); } - $this->filesystem->mkdir(dirname($permanentFilePath), 0755); - $this->filesystem->rename($tempFilePath, $permanentFilePath, true); + $this->fileSystemManager->moveFile($tempFilePath, $permanentFilePath); $chapter->setCbzPath($permanentFilePath); @@ -91,15 +86,6 @@ class MangaImportService return $baseFileName . '.cbz'; } - private function createMangaDirectory(Manga $manga): string - { - $mangaYear = $manga->getPublicationYear() ?? 'unknown'; - $directoryPath = self::CBZ_DIRECTORY . '/' . ucfirst($manga->getSlug()) . ' (' . $mangaYear . ')'; - - $this->filesystem->mkdir($directoryPath, 0755); - return $directoryPath; - } - private function updateVolumeChapters(Manga $manga, int $volume, string $cbzPath): void { $chapters = $this->chapterRepository->findBy([ diff --git a/src/Service/Scraper/AbstractScraper.php b/src/Service/Scraper/AbstractScraper.php index d0844f6..7327eca 100644 --- a/src/Service/Scraper/AbstractScraper.php +++ b/src/Service/Scraper/AbstractScraper.php @@ -6,6 +6,7 @@ use App\Entity\Chapter; use App\Entity\ContentSource; use App\Entity\Manga; use App\Event\PageScrappingProgressEvent; +use App\Manager\FileSystemManager; use Doctrine\ORM\EntityManagerInterface; use Exception; use GuzzleHttp\Client; @@ -15,11 +16,10 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface; abstract class AbstractScraper implements ScraperInterface { - const string PUBLIC_CBZ = '/public/cbz'; protected Client $httpClient; public function __construct( - protected string $projectDir, + protected FileSystemManager $fileSystemManager, protected EventDispatcherInterface $eventDispatcher, protected EntityManagerInterface $entityManager ) @@ -53,7 +53,8 @@ abstract class AbstractScraper implements ScraperInterface protected function generateCbzPath(Manga $manga, Chapter $chapter): string { - $volumeDir = $this->createDirectories($manga, $chapter->getVolume()); + $mangaDir = $this->fileSystemManager->createMangaDirectory($manga->getSlug(), $manga->getPublicationYear()); + $volumeDir = $this->fileSystemManager->createVolumeDirectory($mangaDir, $chapter->getVolume()); $fileName = sprintf('%s_vol%d_ch%s.cbz', $manga->getSlug(), $chapter->getVolume(), @@ -62,7 +63,7 @@ abstract class AbstractScraper implements ScraperInterface return $volumeDir . '/' . $fileName; } - protected function createCbzFile(string $tempDir, array $pageData, string $cbzFilePath): void + protected function createCbzFile(array $pageData, string $cbzFilePath): void { $zip = new \ZipArchive(); @@ -76,26 +77,7 @@ abstract class AbstractScraper implements ScraperInterface protected function cleanupTempFiles(string $directory): void { - $files = glob($directory . '/*'); - foreach ($files as $file) { - if (is_file($file)) { - unlink($file); - } - } - rmdir($directory); - } - - protected function createDirectories(Manga $manga, int $volume): string - { - $mangaYear = $manga->getPublicationYear() ?? 'unknown'; - $mangaDir = sprintf('%s/%s (%s)', $this->projectDir . self::PUBLIC_CBZ, ucfirst($manga->getSlug()), $mangaYear); - $volumeDir = sprintf('%s/volume_%d', $mangaDir, sprintf('%02d', $volume)); - - if (!is_dir($volumeDir)) { - mkdir($volumeDir, 0755, true); - } - - return $volumeDir; + $this->fileSystemManager->deleteDirectory($directory); } protected function cleanImageUrl(string $url): string diff --git a/src/Service/Scraper/HtmlScraper.php b/src/Service/Scraper/HtmlScraper.php index db7e706..ea383aa 100644 --- a/src/Service/Scraper/HtmlScraper.php +++ b/src/Service/Scraper/HtmlScraper.php @@ -13,17 +13,6 @@ use Symfony\Component\DomCrawler\Crawler; class HtmlScraper extends AbstractScraper { - private Client $client; - - public function __construct( - string $projectDir, - EventDispatcherInterface $eventDispatcher, - EntityManagerInterface $entityManager - ) { - parent::__construct($projectDir, $eventDispatcher, $entityManager); - $this->client = new Client(); - } - /** * @throws Exception * @throws GuzzleException @@ -64,7 +53,7 @@ class HtmlScraper extends AbstractScraper } $cbzFilePath = $this->generateCbzPath($manga, $chapter); - $this->createCbzFile($tempDir, $pageData, $cbzFilePath); + $this->createCbzFile($pageData, $cbzFilePath); $chapter->setCbzPath($cbzFilePath); $this->entityManager->persist($chapter); @@ -144,7 +133,7 @@ class HtmlScraper extends AbstractScraper private function fetchHtml(string $url): string { try { - $response = $this->client->get($url, [ + $response = $this->httpClient->get($url, [ 'http_errors' => true, 'allow_redirects' => false ]); diff --git a/src/Service/Scraper/JavascriptScraper.php b/src/Service/Scraper/JavascriptScraper.php index 6bd820d..a9aea02 100644 --- a/src/Service/Scraper/JavascriptScraper.php +++ b/src/Service/Scraper/JavascriptScraper.php @@ -47,7 +47,7 @@ class JavascriptScraper extends AbstractScraper } $cbzFilePath = $this->generateCbzPath($manga, $chapter); - $this->createCbzFile($tempDir, $pageData, $cbzFilePath); + $this->createCbzFile($pageData, $cbzFilePath); $chapter->setCbzPath($cbzFilePath); $this->entityManager->persist($chapter); diff --git a/src/Service/Scraper/MangadexScraper.php b/src/Service/Scraper/MangadexScraper.php index 0badefc..3101390 100644 --- a/src/Service/Scraper/MangadexScraper.php +++ b/src/Service/Scraper/MangadexScraper.php @@ -10,17 +10,6 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface; class MangadexScraper extends AbstractScraper { - private Client $client; - - public function __construct( - string $projectDir, - EventDispatcherInterface $eventDispatcher, - EntityManagerInterface $entityManager - ) { - parent::__construct($projectDir, $eventDispatcher, $entityManager); - $this->client = new Client(); - } - public function scrapeChapter(Chapter $chapter, ContentSource $contentSource): array|bool { $chapterUrl = $contentSource->getBaseUrl() . sprintf($contentSource->getChapterUrlFormat(), $chapter->getExternalId()); @@ -28,7 +17,7 @@ class MangadexScraper extends AbstractScraper $pageData = []; try { - $response = $this->client->get($chapterUrl); + $response = $this->httpClient->get($chapterUrl); $results = json_decode($response->getBody()->getContents(), true); if ($results['result'] !== 'ok' || count($results['chapter']['dataSaver']) === 0) { @@ -54,7 +43,7 @@ class MangadexScraper extends AbstractScraper } $cbzFilePath = $this->generateCbzPath($manga, $chapter); - $this->createCbzFile($tempDir, $pageData, $cbzFilePath); + $this->createCbzFile($pageData, $cbzFilePath); $chapter->setCbzPath($cbzFilePath); $this->entityManager->persist($chapter);