mirror of
https://github.com/moku-project/Moku.git
synced 2026-06-13 09:19:56 -05:00
Poll when updating on server
This commit is contained in:
@@ -75,20 +75,6 @@ export const LIBRARY_UPDATE_STATUS = `
|
|||||||
manga { id title thumbnailUrl unreadCount }
|
manga { id title thumbnailUrl unreadCount }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const GET_LIBRARY_UPDATE_PANEL_STATUS = `
|
|
||||||
query GetLibraryUpdatePanelStatus {
|
|
||||||
libraryUpdateStatus {
|
|
||||||
jobsInfo {
|
|
||||||
isRunning
|
|
||||||
finishedJobs
|
|
||||||
totalJobs
|
|
||||||
skippedMangasCount
|
|
||||||
skippedCategoriesCount
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lastUpdateTimestamp {
|
lastUpdateTimestamp {
|
||||||
timestamp
|
timestamp
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,7 @@
|
|||||||
| `GET_CATEGORIES` | — | All categories with order/settings and their assigned manga (minimal fields) |
|
| `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_DOWNLOADED_CHAPTERS_PAGES` | — | Page counts for all downloaded chapters — used for storage stats |
|
||||||
| `GET_DOWNLOADS_PATH` | — | `downloadsPath` and `localSourcePath` from settings |
|
| `GET_DOWNLOADS_PATH` | — | `downloadsPath` and `localSourcePath` from settings |
|
||||||
| `LIBRARY_UPDATE_STATUS` | — | Current library update job — `jobsInfo` progress and `mangaUpdates` list with new chapters |
|
| `LIBRARY_UPDATE_STATUS` | — | Current library update job (`jobsInfo`, `mangaUpdates`) plus `lastUpdateTimestamp` for server-side update timing |
|
||||||
| `GET_LIBRARY_UPDATE_PANEL_STATUS` | — | Library updater status + server `lastUpdateTimestamp` for UI status displays |
|
|
||||||
| `GET_RESTORE_STATUS` | `id: String!` | Backup restore job status by job ID — `mangaProgress`, `state`, `totalManga` |
|
| `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 |
|
| `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` |
|
| `MANGAS_BY_GENRE` | `filter: MangaFilterInput`, `first: Int`, `offset: Int` | Paginated manga filtered by genre, ordered by `IN_LIBRARY_AT DESC` |
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { onMount, onDestroy } from "svelte";
|
import { onMount, onDestroy } from "svelte";
|
||||||
import { BookOpen, CircleNotch } from "phosphor-svelte";
|
import { BookOpen, CircleNotch } from "phosphor-svelte";
|
||||||
import { gql } from "@api/client";
|
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 { cache, CACHE_GROUPS, CACHE_KEYS } from "@core/cache";
|
||||||
import { store, openReader, setActiveManga, addToast } from "@store/state.svelte";
|
import { store, openReader, setActiveManga, addToast } from "@store/state.svelte";
|
||||||
import { dayLabel } from "@core/util";
|
import { dayLabel } from "@core/util";
|
||||||
@@ -31,9 +31,13 @@
|
|||||||
let openingId = $state<number | null>(null);
|
let openingId = $state<number | null>(null);
|
||||||
let updaterRunning = $state(false);
|
let updaterRunning = $state(false);
|
||||||
let lastUpdatedTs = $state<number | null>(null);
|
let lastUpdatedTs = $state<number | null>(null);
|
||||||
|
let updaterFinishedJobs = $state<number | null>(null);
|
||||||
|
let updaterTotalJobs = $state<number | null>(null);
|
||||||
|
|
||||||
let ctrl: AbortController | null = null;
|
let ctrl: AbortController | null = null;
|
||||||
|
let statusPollTimer: ReturnType<typeof setTimeout> | null = null;
|
||||||
const RECENT_UPDATES_TTL_MS = 60 * 1_000;
|
const RECENT_UPDATES_TTL_MS = 60 * 1_000;
|
||||||
|
const UPDATE_STATUS_POLL_MS = 2_000;
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
onRegisterRefresh?.(() => loadUpdates(true));
|
onRegisterRefresh?.(() => loadUpdates(true));
|
||||||
@@ -42,6 +46,7 @@
|
|||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
ctrl?.abort();
|
ctrl?.abort();
|
||||||
|
stopStatusPolling();
|
||||||
});
|
});
|
||||||
|
|
||||||
function fetchedAtMs(item: Pick<RecentUpdate, "fetchedAt">): number {
|
function fetchedAtMs(item: Pick<RecentUpdate, "fetchedAt">): number {
|
||||||
@@ -71,6 +76,12 @@
|
|||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const updaterProgressLabel = $derived(
|
||||||
|
typeof updaterFinishedJobs === "number" && typeof updaterTotalJobs === "number" && updaterTotalJobs > 0
|
||||||
|
? `${updaterFinishedJobs}/${updaterTotalJobs}`
|
||||||
|
: null
|
||||||
|
);
|
||||||
|
|
||||||
function parseServerTimestamp(value: unknown): number | null {
|
function parseServerTimestamp(value: unknown): number | null {
|
||||||
if (typeof value === "number") return Number.isFinite(value) ? value : null;
|
if (typeof value === "number") return Number.isFinite(value) ? value : null;
|
||||||
if (typeof value === "string") {
|
if (typeof value === "string") {
|
||||||
@@ -82,6 +93,64 @@
|
|||||||
return null;
|
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 {
|
function mangaStub(item: RecentUpdate): Manga {
|
||||||
return {
|
return {
|
||||||
id: item.manga?.id ?? item.mangaId,
|
id: item.manga?.id ?? item.mangaId,
|
||||||
@@ -120,11 +189,12 @@
|
|||||||
jobsInfo: { isRunning: boolean };
|
jobsInfo: { isRunning: boolean };
|
||||||
};
|
};
|
||||||
lastUpdateTimestamp: { timestamp: string | number | null } | null;
|
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;
|
applyUpdateStatus(statusRes);
|
||||||
lastUpdatedTs = parseServerTimestamp(statusRes?.lastUpdateTimestamp?.timestamp ?? null);
|
if (updaterRunning) scheduleStatusPoll();
|
||||||
|
else stopStatusPolling();
|
||||||
|
|
||||||
if (nextCtrl.signal.aborted) return;
|
if (nextCtrl.signal.aborted) return;
|
||||||
|
|
||||||
@@ -137,6 +207,9 @@
|
|||||||
updates = [];
|
updates = [];
|
||||||
updaterRunning = false;
|
updaterRunning = false;
|
||||||
lastUpdatedTs = null;
|
lastUpdatedTs = null;
|
||||||
|
updaterFinishedJobs = null;
|
||||||
|
updaterTotalJobs = null;
|
||||||
|
stopStatusPolling();
|
||||||
} finally {
|
} finally {
|
||||||
if (!nextCtrl.signal.aborted) loading = false;
|
if (!nextCtrl.signal.aborted) loading = false;
|
||||||
}
|
}
|
||||||
@@ -171,9 +244,9 @@
|
|||||||
<div class="root anim-fade-in">
|
<div class="root anim-fade-in">
|
||||||
<div class="bar-wrap">
|
<div class="bar-wrap">
|
||||||
<div class="status-bar">
|
<div class="status-bar">
|
||||||
<div class="status-dot" class:active={loading}></div>
|
<div class="status-dot" class:active={loading || updaterRunning}></div>
|
||||||
<span class="status-text">
|
<span class="status-text">
|
||||||
{#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}
|
||||||
</span>
|
</span>
|
||||||
<div class="status-right">
|
<div class="status-right">
|
||||||
{#if !loading && lastUpdatedLabel}
|
{#if !loading && lastUpdatedLabel}
|
||||||
|
|||||||
Reference in New Issue
Block a user