From 3e102ae933bf2d14b6aada76b3032b553a67708a Mon Sep 17 00:00:00 2001 From: Julien Froidefond Date: Fri, 21 Feb 2025 13:46:54 +0100 Subject: [PATCH] fix: download manager fixes and retries --- src/components/downloads/DownloadManager.tsx | 70 ++++++++++++------ src/components/ui/book-offline-button.tsx | 78 ++++++++++++++------ 2 files changed, 101 insertions(+), 47 deletions(-) diff --git a/src/components/downloads/DownloadManager.tsx b/src/components/downloads/DownloadManager.tsx index 9bc62c0..0198c60 100644 --- a/src/components/downloads/DownloadManager.tsx +++ b/src/components/downloads/DownloadManager.tsx @@ -10,6 +10,7 @@ import { useToast } from "@/components/ui/use-toast"; import { Progress } from "@/components/ui/progress"; import Image from "next/image"; import Link from "next/link"; +import { BookOfflineButton } from "@/components/ui/book-offline-button"; type BookStatus = "idle" | "downloading" | "available" | "error"; @@ -113,9 +114,9 @@ export function DownloadManager() { const handleDeleteBook = async (book: KomgaBook) => { try { const cache = await caches.open("stripstream-books"); - await cache.delete(`/api/komga/books/${book.id}/pages`); + await cache.delete(`/api/komga/images/books/${book.id}/pages`); for (let i = 1; i <= book.media.pagesCount; i++) { - await cache.delete(`/api/komga/books/${book.id}/pages/${i}`); + await cache.delete(`/api/komga/images/books/${book.id}/pages/${i}`); } localStorage.removeItem(getStorageKey(book.id)); setDownloadedBooks((prev) => prev.filter((b) => b.book.id !== book.id)); @@ -153,18 +154,38 @@ export function DownloadManager() { return ( - - Tous ({downloadedBooks.length}) - - En cours ({downloadedBooks.filter((b) => b.status.status === "downloading").length}) - - - Disponibles ({downloadedBooks.filter((b) => b.status.status === "available").length}) - - - Erreurs ({downloadedBooks.filter((b) => b.status.status === "error").length}) - - +
+ + Tous ({downloadedBooks.length}) + + En cours ({downloadedBooks.filter((b) => b.status.status === "downloading").length}) + + + Disponibles ({downloadedBooks.filter((b) => b.status.status === "available").length}) + + + Erreurs ({downloadedBooks.filter((b) => b.status.status === "error").length}) + + + {downloadedBooks.some((b) => b.status.status === "error") && ( + + )} +
{downloadedBooks.map(({ book, status }) => ( @@ -332,15 +353,18 @@ function BookDownloadCard({ book, status, onDelete, onRetry }: BookDownloadCardP )} - + + {status.status !== "downloading" && ( + + )} diff --git a/src/components/ui/book-offline-button.tsx b/src/components/ui/book-offline-button.tsx index 2063eda..3b1f815 100644 --- a/src/components/ui/book-offline-button.tsx +++ b/src/components/ui/book-offline-button.tsx @@ -56,24 +56,44 @@ export function BookOfflineButton({ book, className }: BookOfflineButtonProps) { // Ajoute le livre au cache si on commence depuis le début if (startFromPage === 1) { - const pagesResponse = await fetch(`/api/komga/books/${book.id}/pages`); - await cache.put(`/api/komga/books/${book.id}/pages`, pagesResponse.clone()); + const pagesResponse = await fetch(`/api/komga/images/books/${book.id}/pages/1`); + if (!pagesResponse.ok) throw new Error("Erreur lors de la récupération des pages"); + await cache.put(`/api/komga/images/books/${book.id}/pages`, pagesResponse.clone()); } - // Cache chaque page + // Cache chaque page avec retry let failedPages = 0; for (let i = startFromPage; i <= book.media.pagesCount; i++) { - try { - const pageResponse = await fetch(`/api/komga/books/${book.id}/pages/${i}`); - if (!pageResponse.ok) { - failedPages++; - continue; + let retryCount = 0; + const maxRetries = 3; + + while (retryCount < maxRetries) { + try { + const pageResponse = await fetch(`/api/komga/images/books/${book.id}/pages/${i}`); + if (!pageResponse.ok) { + retryCount++; + if (retryCount === maxRetries) { + failedPages++; + console.error( + `Échec du téléchargement de la page ${i} après ${maxRetries} tentatives` + ); + } + await new Promise((resolve) => setTimeout(resolve, 1000)); // Attendre 1s avant de réessayer + continue; + } + await cache.put(`/api/komga/images/books/${book.id}/pages/${i}`, pageResponse.clone()); + break; // Sortir de la boucle si réussi + } catch (error) { + retryCount++; + if (retryCount === maxRetries) { + failedPages++; + console.error(`Erreur lors du téléchargement de la page ${i}:`, error); + } + await new Promise((resolve) => setTimeout(resolve, 1000)); } - await cache.put(`/api/komga/books/${book.id}/pages/${i}`, pageResponse.clone()); - } catch (error) { - console.error(`Erreur lors du téléchargement de la page ${i}:`, error); - failedPages++; } + + // Mise à jour du statut const progress = (i / book.media.pagesCount) * 100; setDownloadProgress(progress); setBookStatus(book.id, { @@ -82,13 +102,20 @@ export function BookOfflineButton({ book, className }: BookOfflineButtonProps) { timestamp: Date.now(), lastDownloadedPage: i, }); + + // Vérifier si le statut a changé pendant le téléchargement + const currentStatus = getBookStatus(book.id); + if (currentStatus.status === "idle") { + // Le téléchargement a été annulé + throw new Error("Téléchargement annulé"); + } } if (failedPages > 0) { // Si des pages ont échoué, on supprime tout le cache pour ce livre - await cache.delete(`/api/komga/books/${book.id}/pages`); + await cache.delete(`/api/komga/images/books/${book.id}/pages`); for (let i = 1; i <= book.media.pagesCount; i++) { - await cache.delete(`/api/komga/books/${book.id}/pages/${i}`); + await cache.delete(`/api/komga/images/books/${book.id}/pages/${i}`); } setIsAvailableOffline(false); setBookStatus(book.id, { status: "error", progress: 0, timestamp: Date.now() }); @@ -107,12 +134,15 @@ export function BookOfflineButton({ book, className }: BookOfflineButtonProps) { } } catch (error) { console.error("Erreur lors du téléchargement:", error); - setBookStatus(book.id, { status: "error", progress: 0, timestamp: Date.now() }); - toast({ - title: "Erreur", - description: "Une erreur est survenue lors du téléchargement", - variant: "destructive", - }); + // Ne pas changer le statut si le téléchargement a été volontairement annulé + if ((error as Error)?.message !== "Téléchargement annulé") { + setBookStatus(book.id, { status: "error", progress: 0, timestamp: Date.now() }); + toast({ + title: "Erreur", + description: "Une erreur est survenue lors du téléchargement", + variant: "destructive", + }); + } } finally { setIsLoading(false); setDownloadProgress(0); @@ -152,7 +182,7 @@ export function BookOfflineButton({ book, className }: BookOfflineButtonProps) { try { const cache = await caches.open("stripstream-books"); // On vérifie que toutes les pages sont dans le cache - const bookPages = await cache.match(`/api/komga/books/${book.id}/pages`); + const bookPages = await cache.match(`/api/komga/images/books/${book.id}/pages`); if (!bookPages) { setIsAvailableOffline(false); setBookStatus(book.id, { status: "idle", progress: 0, timestamp: Date.now() }); @@ -162,7 +192,7 @@ export function BookOfflineButton({ book, className }: BookOfflineButtonProps) { // Vérifie que toutes les pages sont dans le cache let allPagesAvailable = true; for (let i = 1; i <= book.media.pagesCount; i++) { - const page = await cache.match(`/api/komga/books/${book.id}/pages/${i}`); + const page = await cache.match(`/api/komga/images/books/${book.id}/pages/${i}`); if (!page) { allPagesAvailable = false; break; @@ -200,9 +230,9 @@ export function BookOfflineButton({ book, className }: BookOfflineButtonProps) { if (isAvailableOffline) { setBookStatus(book.id, { status: "idle", progress: 0, timestamp: Date.now() }); // Supprime le livre du cache - await cache.delete(`/api/komga/books/${book.id}/pages`); + await cache.delete(`/api/komga/images/books/${book.id}/pages`); for (let i = 1; i <= book.media.pagesCount; i++) { - await cache.delete(`/api/komga/books/${book.id}/pages/${i}`); + await cache.delete(`/api/komga/images/books/${book.id}/pages/${i}`); const progress = (i / book.media.pagesCount) * 100; setDownloadProgress(progress); }