fix: download manager fixes and retries

This commit is contained in:
Julien Froidefond
2025-02-21 13:46:54 +01:00
parent ade8b372b6
commit 3e102ae933
2 changed files with 101 additions and 47 deletions

View File

@@ -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 (
<Tabs defaultValue="all" className="space-y-4">
<TabsList>
<TabsTrigger value="all">Tous ({downloadedBooks.length})</TabsTrigger>
<TabsTrigger value="downloading">
En cours ({downloadedBooks.filter((b) => b.status.status === "downloading").length})
</TabsTrigger>
<TabsTrigger value="available">
Disponibles ({downloadedBooks.filter((b) => b.status.status === "available").length})
</TabsTrigger>
<TabsTrigger value="error">
Erreurs ({downloadedBooks.filter((b) => b.status.status === "error").length})
</TabsTrigger>
</TabsList>
<div className="flex items-center justify-between">
<TabsList>
<TabsTrigger value="all">Tous ({downloadedBooks.length})</TabsTrigger>
<TabsTrigger value="downloading">
En cours ({downloadedBooks.filter((b) => b.status.status === "downloading").length})
</TabsTrigger>
<TabsTrigger value="available">
Disponibles ({downloadedBooks.filter((b) => b.status.status === "available").length})
</TabsTrigger>
<TabsTrigger value="error">
Erreurs ({downloadedBooks.filter((b) => b.status.status === "error").length})
</TabsTrigger>
</TabsList>
{downloadedBooks.some((b) => b.status.status === "error") && (
<Button
variant="outline"
size="sm"
onClick={() => {
const errorBooks = downloadedBooks.filter((b) => b.status.status === "error");
errorBooks.forEach((book) => handleRetryDownload(book.book));
toast({
title: "Relance des téléchargements",
description: `${errorBooks.length} téléchargement(s) relancé(s)`,
});
}}
className="gap-2"
>
<Download className="h-4 w-4" />
Tout relancer
</Button>
)}
</div>
<TabsContent value="all" className="space-y-4">
{downloadedBooks.map(({ book, status }) => (
@@ -332,15 +353,18 @@ function BookDownloadCard({ book, status, onDelete, onRetry }: BookDownloadCardP
<Download className="h-5 w-5" />
</Button>
)}
<Button
variant="ghost"
size="icon"
onClick={onDelete}
title="Supprimer"
className="h-8 w-8 p-0 rounded-br-lg rounded-tl-lg"
>
<Trash2 className="h-5 w-5" />
</Button>
<BookOfflineButton book={book} />
{status.status !== "downloading" && (
<Button
variant="ghost"
size="icon"
onClick={onDelete}
title="Supprimer"
className="h-8 w-8 p-0 rounded-br-lg rounded-tl-lg"
>
<Trash2 className="h-5 w-5" />
</Button>
)}
</div>
</div>
</Card>

View File

@@ -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);
}