fix: download manager fixes and retries
This commit is contained in:
@@ -10,6 +10,7 @@ import { useToast } from "@/components/ui/use-toast";
|
|||||||
import { Progress } from "@/components/ui/progress";
|
import { Progress } from "@/components/ui/progress";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { BookOfflineButton } from "@/components/ui/book-offline-button";
|
||||||
|
|
||||||
type BookStatus = "idle" | "downloading" | "available" | "error";
|
type BookStatus = "idle" | "downloading" | "available" | "error";
|
||||||
|
|
||||||
@@ -113,9 +114,9 @@ export function DownloadManager() {
|
|||||||
const handleDeleteBook = async (book: KomgaBook) => {
|
const handleDeleteBook = async (book: KomgaBook) => {
|
||||||
try {
|
try {
|
||||||
const cache = await caches.open("stripstream-books");
|
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++) {
|
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));
|
localStorage.removeItem(getStorageKey(book.id));
|
||||||
setDownloadedBooks((prev) => prev.filter((b) => b.book.id !== book.id));
|
setDownloadedBooks((prev) => prev.filter((b) => b.book.id !== book.id));
|
||||||
@@ -153,6 +154,7 @@ export function DownloadManager() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Tabs defaultValue="all" className="space-y-4">
|
<Tabs defaultValue="all" className="space-y-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
<TabsList>
|
<TabsList>
|
||||||
<TabsTrigger value="all">Tous ({downloadedBooks.length})</TabsTrigger>
|
<TabsTrigger value="all">Tous ({downloadedBooks.length})</TabsTrigger>
|
||||||
<TabsTrigger value="downloading">
|
<TabsTrigger value="downloading">
|
||||||
@@ -165,6 +167,25 @@ export function DownloadManager() {
|
|||||||
Erreurs ({downloadedBooks.filter((b) => b.status.status === "error").length})
|
Erreurs ({downloadedBooks.filter((b) => b.status.status === "error").length})
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
</TabsList>
|
</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">
|
<TabsContent value="all" className="space-y-4">
|
||||||
{downloadedBooks.map(({ book, status }) => (
|
{downloadedBooks.map(({ book, status }) => (
|
||||||
@@ -332,6 +353,8 @@ function BookDownloadCard({ book, status, onDelete, onRetry }: BookDownloadCardP
|
|||||||
<Download className="h-5 w-5" />
|
<Download className="h-5 w-5" />
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
<BookOfflineButton book={book} />
|
||||||
|
{status.status !== "downloading" && (
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
@@ -341,6 +364,7 @@ function BookDownloadCard({ book, status, onDelete, onRetry }: BookDownloadCardP
|
|||||||
>
|
>
|
||||||
<Trash2 className="h-5 w-5" />
|
<Trash2 className="h-5 w-5" />
|
||||||
</Button>
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -56,24 +56,44 @@ export function BookOfflineButton({ book, className }: BookOfflineButtonProps) {
|
|||||||
|
|
||||||
// Ajoute le livre au cache si on commence depuis le début
|
// Ajoute le livre au cache si on commence depuis le début
|
||||||
if (startFromPage === 1) {
|
if (startFromPage === 1) {
|
||||||
const pagesResponse = await fetch(`/api/komga/books/${book.id}/pages`);
|
const pagesResponse = await fetch(`/api/komga/images/books/${book.id}/pages/1`);
|
||||||
await cache.put(`/api/komga/books/${book.id}/pages`, pagesResponse.clone());
|
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;
|
let failedPages = 0;
|
||||||
for (let i = startFromPage; i <= book.media.pagesCount; i++) {
|
for (let i = startFromPage; i <= book.media.pagesCount; i++) {
|
||||||
|
let retryCount = 0;
|
||||||
|
const maxRetries = 3;
|
||||||
|
|
||||||
|
while (retryCount < maxRetries) {
|
||||||
try {
|
try {
|
||||||
const pageResponse = await fetch(`/api/komga/books/${book.id}/pages/${i}`);
|
const pageResponse = await fetch(`/api/komga/images/books/${book.id}/pages/${i}`);
|
||||||
if (!pageResponse.ok) {
|
if (!pageResponse.ok) {
|
||||||
|
retryCount++;
|
||||||
|
if (retryCount === maxRetries) {
|
||||||
failedPages++;
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
await cache.put(`/api/komga/books/${book.id}/pages/${i}`, pageResponse.clone());
|
await cache.put(`/api/komga/images/books/${book.id}/pages/${i}`, pageResponse.clone());
|
||||||
|
break; // Sortir de la boucle si réussi
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Erreur lors du téléchargement de la page ${i}:`, error);
|
retryCount++;
|
||||||
|
if (retryCount === maxRetries) {
|
||||||
failedPages++;
|
failedPages++;
|
||||||
|
console.error(`Erreur lors du téléchargement de la page ${i}:`, error);
|
||||||
}
|
}
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mise à jour du statut
|
||||||
const progress = (i / book.media.pagesCount) * 100;
|
const progress = (i / book.media.pagesCount) * 100;
|
||||||
setDownloadProgress(progress);
|
setDownloadProgress(progress);
|
||||||
setBookStatus(book.id, {
|
setBookStatus(book.id, {
|
||||||
@@ -82,13 +102,20 @@ export function BookOfflineButton({ book, className }: BookOfflineButtonProps) {
|
|||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
lastDownloadedPage: i,
|
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) {
|
if (failedPages > 0) {
|
||||||
// Si des pages ont échoué, on supprime tout le cache pour ce livre
|
// 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++) {
|
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);
|
setIsAvailableOffline(false);
|
||||||
setBookStatus(book.id, { status: "error", progress: 0, timestamp: Date.now() });
|
setBookStatus(book.id, { status: "error", progress: 0, timestamp: Date.now() });
|
||||||
@@ -107,12 +134,15 @@ export function BookOfflineButton({ book, className }: BookOfflineButtonProps) {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Erreur lors du téléchargement:", error);
|
console.error("Erreur lors du téléchargement:", error);
|
||||||
|
// 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() });
|
setBookStatus(book.id, { status: "error", progress: 0, timestamp: Date.now() });
|
||||||
toast({
|
toast({
|
||||||
title: "Erreur",
|
title: "Erreur",
|
||||||
description: "Une erreur est survenue lors du téléchargement",
|
description: "Une erreur est survenue lors du téléchargement",
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
setDownloadProgress(0);
|
setDownloadProgress(0);
|
||||||
@@ -152,7 +182,7 @@ export function BookOfflineButton({ book, className }: BookOfflineButtonProps) {
|
|||||||
try {
|
try {
|
||||||
const cache = await caches.open("stripstream-books");
|
const cache = await caches.open("stripstream-books");
|
||||||
// On vérifie que toutes les pages sont dans le cache
|
// 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) {
|
if (!bookPages) {
|
||||||
setIsAvailableOffline(false);
|
setIsAvailableOffline(false);
|
||||||
setBookStatus(book.id, { status: "idle", progress: 0, timestamp: Date.now() });
|
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
|
// Vérifie que toutes les pages sont dans le cache
|
||||||
let allPagesAvailable = true;
|
let allPagesAvailable = true;
|
||||||
for (let i = 1; i <= book.media.pagesCount; i++) {
|
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) {
|
if (!page) {
|
||||||
allPagesAvailable = false;
|
allPagesAvailable = false;
|
||||||
break;
|
break;
|
||||||
@@ -200,9 +230,9 @@ export function BookOfflineButton({ book, className }: BookOfflineButtonProps) {
|
|||||||
if (isAvailableOffline) {
|
if (isAvailableOffline) {
|
||||||
setBookStatus(book.id, { status: "idle", progress: 0, timestamp: Date.now() });
|
setBookStatus(book.id, { status: "idle", progress: 0, timestamp: Date.now() });
|
||||||
// Supprime le livre du cache
|
// 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++) {
|
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;
|
const progress = (i / book.media.pagesCount) * 100;
|
||||||
setDownloadProgress(progress);
|
setDownloadProgress(progress);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user