From 7e447fd8f478fd3f980f9b44ace29abc7fdffb04 Mon Sep 17 00:00:00 2001
From: Fuwn <contact@fuwn.net>
Date: Fri, 27 Mar 2026 08:28:30 +0000
Subject: refactor(proxy): move manga chapter counts behind indexed cache

---
 src/lib/List/Manga/MangaListTemplate.svelte | 81 ++++++++++++++---------------
 1 file changed, 39 insertions(+), 42 deletions(-)

(limited to 'src/lib/List/Manga/MangaListTemplate.svelte')

diff --git a/src/lib/List/Manga/MangaListTemplate.svelte b/src/lib/List/Manga/MangaListTemplate.svelte
index eb7ffd83..7333d24e 100644
--- a/src/lib/List/Manga/MangaListTemplate.svelte
+++ b/src/lib/List/Manga/MangaListTemplate.svelte
@@ -1,25 +1,25 @@
 <script lang="ts">
-import { mediaListCollection, Type, type Media } from "$lib/Data/AniList/media";
-import type { AniListAuthorisation } from "$lib/Data/AniList/identity";
+import localforage from "localforage";
 import { onDestroy, onMount } from "svelte";
-import { chapterCount } from "$lib/Media/Manga/chapters";
-import { pruneAllManga } from "$lib/Media/Manga/cache";
-import manga from "$stores/manga";
+import { browser } from "$app/environment";
+import type { AniListAuthorisation } from "$lib/Data/AniList/identity";
+import { type Media, mediaListCollection, Type } from "$lib/Data/AniList/media";
 import { database } from "$lib/Database/IDB/chapters";
-import settings from "$stores/settings";
-import lastPruneTimes from "$stores/lastPruneTimes";
-import ListTitle from "../ListTitle.svelte";
 import RateLimitedError from "$lib/Error/RateLimited.svelte";
-import CleanMangaList from "./CleanMangaList.svelte";
+import Skeleton from "$lib/Loading/Skeleton.svelte";
 import { incrementMediaProgress } from "$lib/Media/Anime/cache";
-import { addNotification } from "$lib/Notification/store";
+import { pruneAllManga } from "$lib/Media/Manga/cache";
+import { chapterCount, hydrateChapterCounts } from "$lib/Media/Manga/chapters";
 import { options } from "$lib/Notification/options";
-import Skeleton from "$lib/Loading/Skeleton.svelte";
-import locale from "$stores/locale";
-import { browser } from "$app/environment";
-import identity from "$stores/identity";
+import { addNotification } from "$lib/Notification/store";
 import privilegedUser from "$lib/Utility/privilegedUser";
-import localforage from "localforage";
+import identity from "$stores/identity";
+import lastPruneTimes from "$stores/lastPruneTimes";
+import locale from "$stores/locale";
+import manga from "$stores/manga";
+import settings from "$stores/settings";
+import ListTitle from "../ListTitle.svelte";
+import CleanMangaList from "./CleanMangaList.svelte";
 
 export let user: AniListAuthorisation = {
 	accessToken: "",
@@ -78,7 +78,7 @@ onMount(async () => {
 			`last${due ? "" : "Completed"}MangaListLength`,
 		)) as number | null;
 
-		if (lastStoredList) lastListSize = parseInt(String(lastStoredList));
+		if (lastStoredList) lastListSize = parseInt(String(lastStoredList), 10);
 	}
 
 	startTime = performance.now();
@@ -143,6 +143,7 @@ const cleanMedia = async (
 	force: boolean,
 ) => {
 	progress = 0;
+	rateLimited = false;
 
 	if (manga && dummy) return manga;
 
@@ -157,7 +158,7 @@ const cleanMedia = async (
 		if ($lastPruneTimes.chapters === 1) {
 			refreshing = true;
 
-			lastPruneTimes.setKey("chapters", new Date().getTime());
+			lastPruneTimes.setKey("chapters", Date.now());
 		} else {
 			const currentDate = new Date();
 
@@ -196,33 +197,29 @@ const cleanMedia = async (
 				($settings.displayNotStarted === true ? 0 : 1),
 	);
 	let finalMedia = releasingMedia;
-	const progressStep = 100 / finalMedia.length / 2;
-	const chapterPromises = finalMedia.map((m: Media) =>
-		database.chapters.get(m.id).then((c) => {
-			if (progress < 100) progress += progressStep;
-
-			if (!due)
-				return new Promise((resolve) => resolve(m.chapters)) as Promise<
-					number | null
-				>;
-
-			if (c !== undefined)
-				return chapterCount($identity, m, $settings.calculateGuessingDisabled);
-			else {
-				// A = On 1 second interval,
-				// B = a maximum of 5 requests per second are allowed.
-				// C = chapterCount makes 3 requests per call.
-				// F = A / (B / C) = 0.6 seconds
-				return new Promise((resolve) => setTimeout(resolve, 600)).then(() =>
-					chapterCount($identity, m, $settings.calculateGuessingDisabled),
-				);
-			}
-		}),
-	);
 	const chapterCounts: (number | null)[] = [];
 
-	for (let i = 0; i < chapterPromises.length; i++) {
-		const count = await chapterPromises[i];
+	if (due && finalMedia.length > 0) {
+		const hydration = await hydrateChapterCounts(
+			$identity,
+			finalMedia,
+			$settings.calculateGuessingDisabled,
+		);
+
+		rateLimited = hydration.rateLimited;
+		progress = 50;
+	}
+
+	for (let i = 0; i < finalMedia.length; i++) {
+		const media = finalMedia[i];
+		const progressStep = finalMedia.length > 0 ? 50 / finalMedia.length : 0;
+		const count = due
+			? await chapterCount(
+					$identity,
+					media,
+					$settings.calculateGuessingDisabled,
+				)
+			: media.chapters;
 
 		if (count === -22) {
 			rateLimited = true;
-- 
cgit v1.2.3

