From b0efb183e8ac175dfc6a956ce09be842c934c015 Mon Sep 17 00:00:00 2001 From: Zerebos Date: Thu, 21 May 2026 02:43:06 -0400 Subject: [PATCH] Poll when updating on server --- src/api/queries/manga.ts | 14 --- src/api/queries/queries.md | 3 +- .../recent/components/UpdatesPanel.svelte | 85 +++++++++++++++++-- 3 files changed, 80 insertions(+), 22 deletions(-) diff --git a/src/api/queries/manga.ts b/src/api/queries/manga.ts index 4eceda5..ac89539 100644 --- a/src/api/queries/manga.ts +++ b/src/api/queries/manga.ts @@ -75,20 +75,6 @@ export const LIBRARY_UPDATE_STATUS = ` manga { id title thumbnailUrl unreadCount } } } - } -`; - -export const GET_LIBRARY_UPDATE_PANEL_STATUS = ` - query GetLibraryUpdatePanelStatus { - libraryUpdateStatus { - jobsInfo { - isRunning - finishedJobs - totalJobs - skippedMangasCount - skippedCategoriesCount - } - } lastUpdateTimestamp { timestamp } diff --git a/src/api/queries/queries.md b/src/api/queries/queries.md index edbebc8..7c441a8 100644 --- a/src/api/queries/queries.md +++ b/src/api/queries/queries.md @@ -10,8 +10,7 @@ | `GET_CATEGORIES` | — | All categories with order/settings and their assigned manga (minimal fields) | | `GET_DOWNLOADED_CHAPTERS_PAGES` | — | Page counts for all downloaded chapters — used for storage stats | | `GET_DOWNLOADS_PATH` | — | `downloadsPath` and `localSourcePath` from settings | -| `LIBRARY_UPDATE_STATUS` | — | Current library update job — `jobsInfo` progress and `mangaUpdates` list with new chapters | -| `GET_LIBRARY_UPDATE_PANEL_STATUS` | — | Library updater status + server `lastUpdateTimestamp` for UI status displays | +| `LIBRARY_UPDATE_STATUS` | — | Current library update job (`jobsInfo`, `mangaUpdates`) plus `lastUpdateTimestamp` for server-side update timing | | `GET_RESTORE_STATUS` | `id: String!` | Backup restore job status by job ID — `mangaProgress`, `state`, `totalManga` | | `VALIDATE_BACKUP` | `backup: Upload!` | Validate a backup file before restore — returns missing sources and trackers | | `MANGAS_BY_GENRE` | `filter: MangaFilterInput`, `first: Int`, `offset: Int` | Paginated manga filtered by genre, ordered by `IN_LIBRARY_AT DESC` | diff --git a/src/features/recent/components/UpdatesPanel.svelte b/src/features/recent/components/UpdatesPanel.svelte index 2416169..4539637 100644 --- a/src/features/recent/components/UpdatesPanel.svelte +++ b/src/features/recent/components/UpdatesPanel.svelte @@ -2,7 +2,7 @@ import { onMount, onDestroy } from "svelte"; import { BookOpen, CircleNotch } from "phosphor-svelte"; import { gql } from "@api/client"; - import { GET_RECENTLY_UPDATED, GET_CHAPTERS, GET_LIBRARY_UPDATE_PANEL_STATUS } from "@api/queries"; + import { GET_RECENTLY_UPDATED, GET_CHAPTERS, LIBRARY_UPDATE_STATUS } from "@api/queries"; import { cache, CACHE_GROUPS, CACHE_KEYS } from "@core/cache"; import { store, openReader, setActiveManga, addToast } from "@store/state.svelte"; import { dayLabel } from "@core/util"; @@ -31,9 +31,13 @@ let openingId = $state(null); let updaterRunning = $state(false); let lastUpdatedTs = $state(null); + let updaterFinishedJobs = $state(null); + let updaterTotalJobs = $state(null); let ctrl: AbortController | null = null; + let statusPollTimer: ReturnType | null = null; const RECENT_UPDATES_TTL_MS = 60 * 1_000; + const UPDATE_STATUS_POLL_MS = 2_000; onMount(() => { onRegisterRefresh?.(() => loadUpdates(true)); @@ -42,6 +46,7 @@ onDestroy(() => { ctrl?.abort(); + stopStatusPolling(); }); function fetchedAtMs(item: Pick): number { @@ -71,6 +76,12 @@ : null ); + const updaterProgressLabel = $derived( + typeof updaterFinishedJobs === "number" && typeof updaterTotalJobs === "number" && updaterTotalJobs > 0 + ? `${updaterFinishedJobs}/${updaterTotalJobs}` + : null + ); + function parseServerTimestamp(value: unknown): number | null { if (typeof value === "number") return Number.isFinite(value) ? value : null; if (typeof value === "string") { @@ -82,6 +93,64 @@ return null; } + function applyUpdateStatus(statusRes: { + libraryUpdateStatus: { + jobsInfo: { + isRunning: boolean; + finishedJobs?: number; + totalJobs?: number; + }; + }; + lastUpdateTimestamp: { timestamp: string | number | null } | null; + } | null) { + const jobsInfo = statusRes?.libraryUpdateStatus.jobsInfo; + updaterRunning = jobsInfo?.isRunning ?? false; + updaterFinishedJobs = typeof jobsInfo?.finishedJobs === "number" ? jobsInfo.finishedJobs : null; + updaterTotalJobs = typeof jobsInfo?.totalJobs === "number" ? jobsInfo.totalJobs : null; + lastUpdatedTs = parseServerTimestamp(statusRes?.lastUpdateTimestamp?.timestamp ?? null); + } + + function stopStatusPolling() { + if (!statusPollTimer) return; + clearTimeout(statusPollTimer); + statusPollTimer = null; + } + + function scheduleStatusPoll() { + if (statusPollTimer) return; + + const tick = async () => { + statusPollTimer = null; + try { + const statusRes = await gql<{ + libraryUpdateStatus: { + jobsInfo: { + isRunning: boolean; + finishedJobs: number; + totalJobs: number; + }; + }; + lastUpdateTimestamp: { timestamp: string | number | null } | null; + }>(LIBRARY_UPDATE_STATUS, {}); + + const wasRunning = updaterRunning; + applyUpdateStatus(statusRes); + + if (updaterRunning) { + statusPollTimer = setTimeout(tick, UPDATE_STATUS_POLL_MS); + } else if (wasRunning) { + void loadUpdates(true); + } + } catch { + if (updaterRunning) { + statusPollTimer = setTimeout(tick, UPDATE_STATUS_POLL_MS); + } + } + }; + + statusPollTimer = setTimeout(tick, UPDATE_STATUS_POLL_MS); + } + function mangaStub(item: RecentUpdate): Manga { return { id: item.manga?.id ?? item.mangaId, @@ -120,11 +189,12 @@ jobsInfo: { isRunning: boolean }; }; lastUpdateTimestamp: { timestamp: string | number | null } | null; - }>(GET_LIBRARY_UPDATE_PANEL_STATUS, {}, nextCtrl.signal).catch(() => null), + }>(LIBRARY_UPDATE_STATUS, {}, nextCtrl.signal).catch(() => null), ]); - updaterRunning = statusRes?.libraryUpdateStatus.jobsInfo.isRunning ?? false; - lastUpdatedTs = parseServerTimestamp(statusRes?.lastUpdateTimestamp?.timestamp ?? null); + applyUpdateStatus(statusRes); + if (updaterRunning) scheduleStatusPoll(); + else stopStatusPolling(); if (nextCtrl.signal.aborted) return; @@ -137,6 +207,9 @@ updates = []; updaterRunning = false; lastUpdatedTs = null; + updaterFinishedJobs = null; + updaterTotalJobs = null; + stopStatusPolling(); } finally { if (!nextCtrl.signal.aborted) loading = false; } @@ -171,9 +244,9 @@
-
+
- {#if loading}Checking for updates…{:else if error}Update check failed{:else if updaterRunning}Library update in progress{:else}Up to date{/if} + {#if loading}Checking for updates…{:else if error}Update check failed{:else if updaterRunning}Library update in progress...{#if updaterProgressLabel} ({updaterProgressLabel}){/if}{:else}Up to date{/if}
{#if !loading && lastUpdatedLabel}