From d23c82631edbec8eeebe6d740e9a21716e89cc41 Mon Sep 17 00:00:00 2001 From: "ext.jeremy.guillot@maxicoffee.domains" Date: Sun, 29 Jun 2025 23:35:22 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20ajout=20de=20la=20fonctionnalit=C3=A9?= =?UTF-8?q?=20de=20t=C3=A9l=C3=A9chargement=20des=20volumes=20de=20manga,?= =?UTF-8?q?=20avec=20mise=20=C3=A0=20jour=20de=20l'API=20et=20des=20compos?= =?UTF-8?q?ants=20pour=20g=C3=A9rer=20l'indicateur=20de=20chargement=20et?= =?UTF-8?q?=20le=20t=C3=A9l=C3=A9chargement=20des=20fichiers.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../manga/application/store/mangaStore.js | 10 ++++ .../infrastructure/api/apiMangaRepository.js | 40 +++++++++++++++ .../presentation/components/MangaVolume.vue | 26 ++++++++-- .../Infrastructure/Service/FileService.php | 50 +++++++++++++++++-- 4 files changed, 119 insertions(+), 7 deletions(-) diff --git a/assets/vue/app/domain/manga/application/store/mangaStore.js b/assets/vue/app/domain/manga/application/store/mangaStore.js index b8c4926..81450c5 100644 --- a/assets/vue/app/domain/manga/application/store/mangaStore.js +++ b/assets/vue/app/domain/manga/application/store/mangaStore.js @@ -231,6 +231,16 @@ export const useMangaStore = defineStore('manga', { console.error('Erreur lors du masquage du chapitre:', error); throw error; } + }, + + // --- Download Volume Action --- + async downloadVolume(mangaId, volumeNumber) { + try { + await mangaRepository.downloadVolume(mangaId, volumeNumber); + } catch (error) { + console.error('Erreur lors du téléchargement du volume:', error); + throw error; + } } } }); diff --git a/assets/vue/app/domain/manga/infrastructure/api/apiMangaRepository.js b/assets/vue/app/domain/manga/infrastructure/api/apiMangaRepository.js index c86c67c..c6f6fcc 100644 --- a/assets/vue/app/domain/manga/infrastructure/api/apiMangaRepository.js +++ b/assets/vue/app/domain/manga/infrastructure/api/apiMangaRepository.js @@ -243,4 +243,44 @@ export class ApiMangaRepository { throw error; } } + + async downloadVolume(mangaId, volumeNumber) { + try { + const response = await fetch(`/api/mangas/${mangaId}/volumes/${volumeNumber}/download`); + if (!response.ok) { + throw new Error('Failed to download volume'); + } + + // Récupérer le nom du fichier depuis les headers + const contentDisposition = response.headers.get('Content-Disposition'); + let filename = `volume-${volumeNumber}.zip`; + + if (contentDisposition) { + const filenameMatch = contentDisposition.match(/filename="?(.+)"?/); + if (filenameMatch) { + filename = filenameMatch[1]; + } + } + + // Créer un blob à partir de la réponse + const blob = await response.blob(); + + // Créer un lien de téléchargement temporaire + const url = window.URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = filename; + document.body.appendChild(link); + link.click(); + + // Nettoyer + document.body.removeChild(link); + window.URL.revokeObjectURL(url); + + return true; + } catch (error) { + console.error('API Error:', error); + throw error; + } + } } diff --git a/assets/vue/app/domain/manga/presentation/components/MangaVolume.vue b/assets/vue/app/domain/manga/presentation/components/MangaVolume.vue index abc1da6..6dc9b1a 100644 --- a/assets/vue/app/domain/manga/presentation/components/MangaVolume.vue +++ b/assets/vue/app/domain/manga/presentation/components/MangaVolume.vue @@ -41,8 +41,15 @@ }"> - @@ -93,6 +100,7 @@ import MangaChapterList from './MangaChapterList.vue'; const store = useMangaStore(); const isOpen = ref(props.isOpen); const isSearching = ref(false); + const isDownloading = ref(false); const toggleVolume = () => { isOpen.value = !isOpen.value; @@ -137,7 +145,17 @@ import MangaChapterList from './MangaChapterList.vue'; }; const handleDownload = async () => { - // TODO: Implémenter le téléchargement du volume - console.log('Téléchargement du volume:', props.volume.number); + try { + console.log(`MangaVolume: Téléchargement du volume ${props.volume.number} (Manga ID: ${props.mangaId})`); + // Montrer l'indicateur de chargement + isDownloading.value = true; + + await store.downloadVolume(props.mangaId, props.volume.number); + } catch (error) { + console.error('Erreur lors du téléchargement du volume:', error); + } finally { + // Arrêter l'indicateur de chargement + isDownloading.value = false; + } }; diff --git a/src/Domain/Manga/Infrastructure/Service/FileService.php b/src/Domain/Manga/Infrastructure/Service/FileService.php index 25eeb5d..3a1b3b6 100644 --- a/src/Domain/Manga/Infrastructure/Service/FileService.php +++ b/src/Domain/Manga/Infrastructure/Service/FileService.php @@ -39,11 +39,47 @@ readonly class FileService implements FileServiceInterface throw new \RuntimeException('Cannot create CBZ file'); } + $imageCounter = 1; + foreach ($cbzPaths as $cbzPath) { - if ($this->cbzExists($cbzPath)) { - $filename = basename($cbzPath); - $cbz->addFile($cbzPath, $filename); + if (!$this->cbzExists($cbzPath)) { + continue; } + + $sourceCbz = new \ZipArchive(); + if ($sourceCbz->open($cbzPath) !== true) { + continue; // Skip if we can't open the CBZ + } + + // Extract all images from the current CBZ + for ($i = 0; $i < $sourceCbz->numFiles; $i++) { + $fileName = $sourceCbz->getNameIndex($i); + $fileInfo = $sourceCbz->statIndex($i); + + // Skip directories and non-image files + if ($fileInfo['size'] === 0 || !$this->isImageFile($fileName)) { + continue; + } + + // Get the file content + $imageContent = $sourceCbz->getFromIndex($i); + if ($imageContent === false) { + continue; + } + + // Get file extension + $extension = pathinfo($fileName, PATHINFO_EXTENSION); + + // Create a new filename with proper ordering + $newFileName = sprintf('%04d.%s', $imageCounter, $extension); + + // Add the image to the volume CBZ + $cbz->addFromString($newFileName, $imageContent); + + $imageCounter++; + } + + $sourceCbz->close(); } $cbz->close(); @@ -61,6 +97,14 @@ readonly class FileService implements FileServiceInterface return $response; } + private function isImageFile(string $fileName): bool + { + $imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']; + $extension = strtolower(pathinfo($fileName, PATHINFO_EXTENSION)); + + return in_array($extension, $imageExtensions); + } + public function deleteCbzFile(string $filePath): bool { if (!$this->cbzExists($filePath)) {