fix: cache file KO if reload
This commit is contained in:
15
src/app/api/komga/cache/clear/route.ts
vendored
15
src/app/api/komga/cache/clear/route.ts
vendored
@@ -1,15 +1,8 @@
|
|||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
import { serverCacheService } from "@/lib/services/server-cache.service";
|
import { getServerCacheService } from "@/lib/services/server-cache.service";
|
||||||
|
|
||||||
export async function POST() {
|
export async function POST() {
|
||||||
try {
|
const cacheService = await getServerCacheService();
|
||||||
serverCacheService.clear();
|
cacheService.clear();
|
||||||
return NextResponse.json({ message: "Cache serveur supprimé avec succès" });
|
return NextResponse.json({ message: "Cache cleared" });
|
||||||
} catch (error) {
|
|
||||||
console.error("Erreur lors de la suppression du cache serveur:", error);
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: "Erreur lors de la suppression du cache serveur" },
|
|
||||||
{ status: 500 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
10
src/app/api/komga/cache/mode/route.ts
vendored
10
src/app/api/komga/cache/mode/route.ts
vendored
@@ -1,8 +1,9 @@
|
|||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
import { serverCacheService } from "@/lib/services/server-cache.service";
|
import { getServerCacheService } from "@/lib/services/server-cache.service";
|
||||||
|
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
return NextResponse.json({ mode: serverCacheService.getCacheMode() });
|
const cacheService = await getServerCacheService();
|
||||||
|
return NextResponse.json({ mode: cacheService.getCacheMode() });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function POST(request: Request) {
|
export async function POST(request: Request) {
|
||||||
@@ -15,8 +16,9 @@ export async function POST(request: Request) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
serverCacheService.setCacheMode(mode);
|
const cacheService = await getServerCacheService();
|
||||||
return NextResponse.json({ mode: serverCacheService.getCacheMode() });
|
cacheService.setCacheMode(mode);
|
||||||
|
return NextResponse.json({ mode: cacheService.getCacheMode() });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Erreur lors de la mise à jour du mode de cache:", error);
|
console.error("Erreur lors de la mise à jour du mode de cache:", error);
|
||||||
return NextResponse.json({ error: "Invalid request" }, { status: 400 });
|
return NextResponse.json({ error: "Invalid request" }, { status: 400 });
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ async function refreshLibrary(libraryId: string) {
|
|||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await LibraryService.clearLibrarySeriesCache(libraryId);
|
await LibraryService.invalidateLibrarySeriesCache(libraryId);
|
||||||
|
|
||||||
revalidatePath(`/libraries/${libraryId}`);
|
revalidatePath(`/libraries/${libraryId}`);
|
||||||
return { success: true };
|
return { success: true };
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ async function refreshHome() {
|
|||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await HomeService.clearHomeCache();
|
await HomeService.invalidateHomeCache();
|
||||||
revalidatePath("/");
|
revalidatePath("/");
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ async function refreshSeries(seriesId: string) {
|
|||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await SeriesService.clearSeriesBooksCache(seriesId);
|
await SeriesService.invalidateSeriesBooksCache(seriesId);
|
||||||
await SeriesService.clearSeriesCache(seriesId);
|
await SeriesService.invalidateSeriesCache(seriesId);
|
||||||
revalidatePath(`/series/${seriesId}`);
|
revalidatePath(`/series/${seriesId}`);
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState, useCallback } from "react";
|
||||||
import { KomgaBook } from "@/types/komga";
|
import { KomgaBook } from "@/types/komga";
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
import { Card } from "@/components/ui/card";
|
import { Card } from "@/components/ui/card";
|
||||||
@@ -31,12 +31,11 @@ export function DownloadManager() {
|
|||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
const getStorageKey = (bookId: string) => `book-status-${bookId}`;
|
const getStorageKey = useCallback((bookId: string) => `book-status-${bookId}`, []);
|
||||||
|
|
||||||
const loadDownloadedBooks = async () => {
|
const loadDownloadedBooks = useCallback(async () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
// Récupère tous les livres du localStorage
|
|
||||||
const books: DownloadedBook[] = [];
|
const books: DownloadedBook[] = [];
|
||||||
for (let i = 0; i < localStorage.length; i++) {
|
for (let i = 0; i < localStorage.length; i++) {
|
||||||
const key = localStorage.key(i);
|
const key = localStorage.key(i);
|
||||||
@@ -70,9 +69,9 @@ export function DownloadManager() {
|
|||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
};
|
}, [toast]);
|
||||||
|
|
||||||
const updateBookStatuses = () => {
|
const updateBookStatuses = useCallback(() => {
|
||||||
setDownloadedBooks((prevBooks) => {
|
setDownloadedBooks((prevBooks) => {
|
||||||
return prevBooks.map((downloadedBook) => {
|
return prevBooks.map((downloadedBook) => {
|
||||||
const status = JSON.parse(
|
const status = JSON.parse(
|
||||||
@@ -87,29 +86,25 @@ export function DownloadManager() {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
}, [getStorageKey]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadDownloadedBooks();
|
loadDownloadedBooks();
|
||||||
|
|
||||||
// Écoute les changements de statut des livres
|
|
||||||
const handleStorageChange = (e: StorageEvent) => {
|
const handleStorageChange = (e: StorageEvent) => {
|
||||||
if (e.key?.startsWith("book-status-")) {
|
if (e.key?.startsWith("book-status-")) {
|
||||||
updateBookStatuses();
|
updateBookStatuses();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Écoute les changements dans d'autres onglets
|
|
||||||
window.addEventListener("storage", handleStorageChange);
|
window.addEventListener("storage", handleStorageChange);
|
||||||
|
|
||||||
// Écoute les changements dans l'onglet courant
|
|
||||||
const interval = setInterval(updateBookStatuses, 1000);
|
const interval = setInterval(updateBookStatuses, 1000);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener("storage", handleStorageChange);
|
window.removeEventListener("storage", handleStorageChange);
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
};
|
};
|
||||||
}, []);
|
}, [loadDownloadedBooks, updateBookStatuses]);
|
||||||
|
|
||||||
const handleDeleteBook = async (book: KomgaBook) => {
|
const handleDeleteBook = async (book: KomgaBook) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect, useCallback } from "react";
|
||||||
import { Download, Check, Loader2 } from "lucide-react";
|
import { Download, Check, Loader2 } from "lucide-react";
|
||||||
import { Button } from "./button";
|
import { Button } from "./button";
|
||||||
import { useToast } from "./use-toast";
|
import { useToast } from "./use-toast";
|
||||||
@@ -27,156 +27,141 @@ export function BookOfflineButton({ book, className }: BookOfflineButtonProps) {
|
|||||||
const [downloadProgress, setDownloadProgress] = useState(0);
|
const [downloadProgress, setDownloadProgress] = useState(0);
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
const getStorageKey = (bookId: string) => `book-status-${bookId}`;
|
const getStorageKey = useCallback((bookId: string) => `book-status-${bookId}`, []);
|
||||||
|
|
||||||
const getBookStatus = (bookId: string): BookDownloadStatus => {
|
const getBookStatus = useCallback(
|
||||||
try {
|
(bookId: string): BookDownloadStatus => {
|
||||||
const status = localStorage.getItem(getStorageKey(bookId));
|
try {
|
||||||
return status ? JSON.parse(status) : { status: "idle", progress: 0, timestamp: 0 };
|
const status = localStorage.getItem(getStorageKey(bookId));
|
||||||
} catch {
|
return status ? JSON.parse(status) : { status: "idle", progress: 0, timestamp: 0 };
|
||||||
return { status: "idle", progress: 0, timestamp: 0 };
|
} catch {
|
||||||
}
|
return { status: "idle", progress: 0, timestamp: 0 };
|
||||||
};
|
|
||||||
|
|
||||||
const setBookStatus = (bookId: string, status: BookDownloadStatus) => {
|
|
||||||
localStorage.setItem(getStorageKey(bookId), JSON.stringify(status));
|
|
||||||
};
|
|
||||||
|
|
||||||
const downloadBook = async (startFromPage: number = 1) => {
|
|
||||||
try {
|
|
||||||
const cache = await caches.open("stripstream-books");
|
|
||||||
|
|
||||||
// Marque le début du téléchargement
|
|
||||||
setBookStatus(book.id, {
|
|
||||||
status: "downloading",
|
|
||||||
progress: ((startFromPage - 1) / book.media.pagesCount) * 100,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
lastDownloadedPage: startFromPage - 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Ajoute le livre au cache si on commence depuis le début
|
|
||||||
if (startFromPage === 1) {
|
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
[getStorageKey]
|
||||||
|
);
|
||||||
|
|
||||||
// Cache chaque page avec retry
|
const setBookStatus = useCallback(
|
||||||
let failedPages = 0;
|
(bookId: string, status: BookDownloadStatus) => {
|
||||||
for (let i = startFromPage; i <= book.media.pagesCount; i++) {
|
localStorage.setItem(getStorageKey(bookId), JSON.stringify(status));
|
||||||
let retryCount = 0;
|
},
|
||||||
const maxRetries = 3;
|
[getStorageKey]
|
||||||
|
);
|
||||||
|
|
||||||
while (retryCount < maxRetries) {
|
const downloadBook = useCallback(
|
||||||
try {
|
async (startFromPage: number = 1) => {
|
||||||
const pageResponse = await fetch(`/api/komga/images/books/${book.id}/pages/${i}`);
|
try {
|
||||||
if (!pageResponse.ok) {
|
const cache = await caches.open("stripstream-books");
|
||||||
|
|
||||||
|
// Marque le début du téléchargement
|
||||||
|
setBookStatus(book.id, {
|
||||||
|
status: "downloading",
|
||||||
|
progress: ((startFromPage - 1) / book.media.pagesCount) * 100,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
lastDownloadedPage: startFromPage - 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Ajoute le livre au cache si on commence depuis le début
|
||||||
|
if (startFromPage === 1) {
|
||||||
|
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 avec retry
|
||||||
|
let failedPages = 0;
|
||||||
|
for (let i = startFromPage; i <= book.media.pagesCount; i++) {
|
||||||
|
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++;
|
retryCount++;
|
||||||
if (retryCount === maxRetries) {
|
if (retryCount === maxRetries) {
|
||||||
failedPages++;
|
failedPages++;
|
||||||
console.error(
|
console.error(`Erreur lors du téléchargement de la page ${i}:`, 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
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
await cache.put(`/api/komga/images/books/${book.id}/pages/${i}`, pageResponse.clone());
|
}
|
||||||
break; // Sortir de la boucle si réussi
|
|
||||||
} catch (error) {
|
// Mise à jour du statut
|
||||||
retryCount++;
|
const progress = (i / book.media.pagesCount) * 100;
|
||||||
if (retryCount === maxRetries) {
|
setDownloadProgress(progress);
|
||||||
failedPages++;
|
setBookStatus(book.id, {
|
||||||
console.error(`Erreur lors du téléchargement de la page ${i}:`, error);
|
status: "downloading",
|
||||||
}
|
progress,
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
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é");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mise à jour du statut
|
if (failedPages > 0) {
|
||||||
const progress = (i / book.media.pagesCount) * 100;
|
// Si des pages ont échoué, on supprime tout le cache pour ce livre
|
||||||
setDownloadProgress(progress);
|
await cache.delete(`/api/komga/images/books/${book.id}/pages`);
|
||||||
setBookStatus(book.id, {
|
for (let i = 1; i <= book.media.pagesCount; i++) {
|
||||||
status: "downloading",
|
await cache.delete(`/api/komga/images/books/${book.id}/pages/${i}`);
|
||||||
progress,
|
}
|
||||||
timestamp: Date.now(),
|
setIsAvailableOffline(false);
|
||||||
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/images/books/${book.id}/pages`);
|
|
||||||
for (let i = 1; i <= book.media.pagesCount; i++) {
|
|
||||||
await cache.delete(`/api/komga/images/books/${book.id}/pages/${i}`);
|
|
||||||
}
|
|
||||||
setIsAvailableOffline(false);
|
|
||||||
setBookStatus(book.id, { status: "error", progress: 0, timestamp: Date.now() });
|
|
||||||
toast({
|
|
||||||
title: "Erreur",
|
|
||||||
description: `${failedPages} page(s) n'ont pas pu être téléchargées. Le livre ne sera pas disponible hors ligne.`,
|
|
||||||
variant: "destructive",
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setIsAvailableOffline(true);
|
|
||||||
setBookStatus(book.id, { status: "available", progress: 100, timestamp: Date.now() });
|
|
||||||
toast({
|
|
||||||
title: "Livre téléchargé",
|
|
||||||
description: "Le livre est maintenant disponible hors ligne",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (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() });
|
|
||||||
toast({
|
|
||||||
title: "Erreur",
|
|
||||||
description: "Une erreur est survenue lors du téléchargement",
|
|
||||||
variant: "destructive",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
setIsLoading(false);
|
|
||||||
setDownloadProgress(0);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Vérifie si le livre est déjà disponible hors ligne
|
|
||||||
useEffect(() => {
|
|
||||||
const checkStatus = async () => {
|
|
||||||
const storedStatus = getBookStatus(book.id);
|
|
||||||
|
|
||||||
// Si le livre est marqué comme en cours de téléchargement
|
|
||||||
if (storedStatus.status === "downloading") {
|
|
||||||
// Si le téléchargement a commencé il y a plus de 5 minutes, on considère qu'il a échoué
|
|
||||||
if (Date.now() - storedStatus.timestamp > 5 * 60 * 1000) {
|
|
||||||
setBookStatus(book.id, { status: "error", progress: 0, timestamp: Date.now() });
|
setBookStatus(book.id, { status: "error", progress: 0, timestamp: Date.now() });
|
||||||
setIsLoading(false);
|
toast({
|
||||||
setDownloadProgress(0);
|
title: "Erreur",
|
||||||
|
description: `${failedPages} page(s) n'ont pas pu être téléchargées. Le livre ne sera pas disponible hors ligne.`,
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
// On reprend le téléchargement là où il s'était arrêté
|
setIsAvailableOffline(true);
|
||||||
setIsLoading(true);
|
setBookStatus(book.id, { status: "available", progress: 100, timestamp: Date.now() });
|
||||||
setDownloadProgress(storedStatus.progress);
|
toast({
|
||||||
const startFromPage = (storedStatus.lastDownloadedPage || 0) + 1;
|
title: "Livre téléchargé",
|
||||||
downloadBook(startFromPage);
|
description: "Le livre est maintenant disponible hors ligne",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
} catch (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() });
|
||||||
|
toast({
|
||||||
|
title: "Erreur",
|
||||||
|
description: "Une erreur est survenue lors du téléchargement",
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
setDownloadProgress(0);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
[book.id, book.media.pagesCount, getBookStatus, setBookStatus, toast]
|
||||||
|
);
|
||||||
|
|
||||||
await checkOfflineAvailability();
|
const checkOfflineAvailability = useCallback(async () => {
|
||||||
};
|
|
||||||
|
|
||||||
checkStatus();
|
|
||||||
}, [book.id]);
|
|
||||||
|
|
||||||
const checkOfflineAvailability = async () => {
|
|
||||||
if (!("caches" in window)) return;
|
if (!("caches" in window)) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -209,7 +194,30 @@ export function BookOfflineButton({ book, className }: BookOfflineButtonProps) {
|
|||||||
console.error("Erreur lors de la vérification du cache:", error);
|
console.error("Erreur lors de la vérification du cache:", error);
|
||||||
setBookStatus(book.id, { status: "error", progress: 0, timestamp: Date.now() });
|
setBookStatus(book.id, { status: "error", progress: 0, timestamp: Date.now() });
|
||||||
}
|
}
|
||||||
};
|
}, [book.id, book.media.pagesCount, setBookStatus]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const checkStatus = async () => {
|
||||||
|
const storedStatus = getBookStatus(book.id);
|
||||||
|
|
||||||
|
if (storedStatus.status === "downloading") {
|
||||||
|
if (Date.now() - storedStatus.timestamp > 5 * 60 * 1000) {
|
||||||
|
setBookStatus(book.id, { status: "error", progress: 0, timestamp: Date.now() });
|
||||||
|
setIsLoading(false);
|
||||||
|
setDownloadProgress(0);
|
||||||
|
} else {
|
||||||
|
setIsLoading(true);
|
||||||
|
setDownloadProgress(storedStatus.progress);
|
||||||
|
const startFromPage = (storedStatus.lastDownloadedPage || 0) + 1;
|
||||||
|
downloadBook(startFromPage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await checkOfflineAvailability();
|
||||||
|
};
|
||||||
|
|
||||||
|
checkStatus();
|
||||||
|
}, [book.id, checkOfflineAvailability, downloadBook, getBookStatus, setBookStatus]);
|
||||||
|
|
||||||
const handleToggleOffline = async () => {
|
const handleToggleOffline = async () => {
|
||||||
if (!("caches" in window)) {
|
if (!("caches" in window)) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { AuthConfig } from "@/types/auth";
|
import { AuthConfig } from "@/types/auth";
|
||||||
import { serverCacheService } from "./server-cache.service";
|
import { getServerCacheService } from "./server-cache.service";
|
||||||
import { ConfigDBService } from "./config-db.service";
|
import { ConfigDBService } from "./config-db.service";
|
||||||
|
|
||||||
// Types de cache disponibles
|
// Types de cache disponibles
|
||||||
@@ -51,7 +51,8 @@ export abstract class BaseApiService {
|
|||||||
fetcher: () => Promise<T>,
|
fetcher: () => Promise<T>,
|
||||||
type: CacheType = "DEFAULT"
|
type: CacheType = "DEFAULT"
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
return serverCacheService.getOrSet(key, fetcher, type);
|
const cacheService = await getServerCacheService();
|
||||||
|
return cacheService.getOrSet(key, fetcher, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static handleError(error: unknown, defaultMessage: string): never {
|
protected static handleError(error: unknown, defaultMessage: string): never {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { BaseApiService } from "./base-api.service";
|
import { BaseApiService } from "./base-api.service";
|
||||||
import { KomgaBook, KomgaSeries } from "@/types/komga";
|
import { KomgaBook, KomgaSeries } from "@/types/komga";
|
||||||
import { LibraryResponse } from "@/types/library";
|
import { LibraryResponse } from "@/types/library";
|
||||||
import { serverCacheService } from "./server-cache.service";
|
import { getServerCacheService } from "./server-cache.service";
|
||||||
|
|
||||||
interface HomeData {
|
interface HomeData {
|
||||||
ongoing: KomgaSeries[];
|
ongoing: KomgaSeries[];
|
||||||
@@ -67,9 +67,10 @@ export class HomeService extends BaseApiService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async clearHomeCache() {
|
static async invalidateHomeCache(): Promise<void> {
|
||||||
serverCacheService.delete("home-ongoing");
|
const cacheService = await getServerCacheService();
|
||||||
serverCacheService.delete("home-recently-read");
|
cacheService.delete("home-ongoing");
|
||||||
serverCacheService.delete("home-on-deck");
|
cacheService.delete("home-recently-read");
|
||||||
|
cacheService.delete("home-on-deck");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { BaseApiService } from "./base-api.service";
|
import { BaseApiService } from "./base-api.service";
|
||||||
import { Library, LibraryResponse } from "@/types/library";
|
import { Library, LibraryResponse } from "@/types/library";
|
||||||
import { Series } from "@/types/series";
|
import { Series } from "@/types/series";
|
||||||
import { serverCacheService } from "./server-cache.service";
|
import { getServerCacheService } from "./server-cache.service";
|
||||||
|
|
||||||
export class LibraryService extends BaseApiService {
|
export class LibraryService extends BaseApiService {
|
||||||
static async getLibraries(): Promise<Library[]> {
|
static async getLibraries(): Promise<Library[]> {
|
||||||
@@ -134,7 +134,8 @@ export class LibraryService extends BaseApiService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async clearLibrarySeriesCache(libraryId: string) {
|
static async invalidateLibrarySeriesCache(libraryId: string): Promise<void> {
|
||||||
serverCacheService.delete(`library-${libraryId}-all-series`);
|
const cacheService = await getServerCacheService();
|
||||||
|
cacheService.delete(`library-${libraryId}-all-series`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { KomgaBook, KomgaSeries } from "@/types/komga";
|
|||||||
import { BookService } from "./book.service";
|
import { BookService } from "./book.service";
|
||||||
import { ImageService } from "./image.service";
|
import { ImageService } from "./image.service";
|
||||||
import { PreferencesService } from "./preferences.service";
|
import { PreferencesService } from "./preferences.service";
|
||||||
import { serverCacheService } from "./server-cache.service";
|
import { getServerCacheService } from "./server-cache.service";
|
||||||
|
|
||||||
export class SeriesService extends BaseApiService {
|
export class SeriesService extends BaseApiService {
|
||||||
static async getSeries(seriesId: string): Promise<KomgaSeries> {
|
static async getSeries(seriesId: string): Promise<KomgaSeries> {
|
||||||
@@ -19,8 +19,9 @@ export class SeriesService extends BaseApiService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async clearSeriesCache(seriesId: string) {
|
static async invalidateSeriesCache(seriesId: string): Promise<void> {
|
||||||
serverCacheService.delete(`series-${seriesId}`);
|
const cacheService = await getServerCacheService();
|
||||||
|
cacheService.delete(`series-${seriesId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getAllSeriesBooks(seriesId: string): Promise<KomgaBook[]> {
|
static async getAllSeriesBooks(seriesId: string): Promise<KomgaBook[]> {
|
||||||
@@ -125,8 +126,9 @@ export class SeriesService extends BaseApiService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async clearSeriesBooksCache(seriesId: string) {
|
static async invalidateSeriesBooksCache(seriesId: string): Promise<void> {
|
||||||
serverCacheService.delete(`series-${seriesId}-all-books`);
|
const cacheService = await getServerCacheService();
|
||||||
|
cacheService.delete(`series-${seriesId}-all-books`);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getFirstBook(seriesId: string): Promise<string> {
|
static async getFirstBook(seriesId: string): Promise<string> {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
import { PreferencesService } from "./preferences.service";
|
||||||
|
|
||||||
type CacheMode = "file" | "memory";
|
type CacheMode = "file" | "memory";
|
||||||
|
|
||||||
@@ -37,6 +38,17 @@ class ServerCacheService {
|
|||||||
this.cacheDir = path.join(process.cwd(), ".cache");
|
this.cacheDir = path.join(process.cwd(), ".cache");
|
||||||
this.ensureCacheDirectory();
|
this.ensureCacheDirectory();
|
||||||
this.cleanExpiredCache();
|
this.cleanExpiredCache();
|
||||||
|
this.initializeCacheMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async initializeCacheMode(): Promise<void> {
|
||||||
|
try {
|
||||||
|
const preferences = await PreferencesService.getPreferences();
|
||||||
|
this.setCacheMode(preferences.cacheMode);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error initializing cache mode from preferences:", error);
|
||||||
|
// Keep default memory mode if preferences can't be loaded
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ensureCacheDirectory(): void {
|
private ensureCacheDirectory(): void {
|
||||||
@@ -117,9 +129,10 @@ class ServerCacheService {
|
|||||||
cleanDirectory(this.cacheDir);
|
cleanDirectory(this.cacheDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static getInstance(): ServerCacheService {
|
public static async getInstance(): Promise<ServerCacheService> {
|
||||||
if (!ServerCacheService.instance) {
|
if (!ServerCacheService.instance) {
|
||||||
ServerCacheService.instance = new ServerCacheService();
|
ServerCacheService.instance = new ServerCacheService();
|
||||||
|
await ServerCacheService.instance.initializeCacheMode();
|
||||||
}
|
}
|
||||||
return ServerCacheService.instance;
|
return ServerCacheService.instance;
|
||||||
}
|
}
|
||||||
@@ -376,4 +389,15 @@ class ServerCacheService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const serverCacheService = ServerCacheService.getInstance();
|
// Créer une instance initialisée du service
|
||||||
|
let initializedInstance: Promise<ServerCacheService>;
|
||||||
|
|
||||||
|
export const getServerCacheService = async (): Promise<ServerCacheService> => {
|
||||||
|
if (!initializedInstance) {
|
||||||
|
initializedInstance = ServerCacheService.getInstance();
|
||||||
|
}
|
||||||
|
return initializedInstance;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Exporter aussi la classe pour les tests
|
||||||
|
export { ServerCacheService };
|
||||||
|
|||||||
Reference in New Issue
Block a user