mirror of
https://github.com/moku-project/Moku.git
synced 2026-06-13 01:09:56 -05:00
Fix: Attempt at fixing Library Refresh
This commit is contained in:
@@ -2,22 +2,15 @@ Major Revisions:
|
|||||||
- Moku-Share to Easily Migrate/Share Manga (Investigate Usecase/Feasibility)
|
- Moku-Share to Easily Migrate/Share Manga (Investigate Usecase/Feasibility)
|
||||||
|
|
||||||
Minor Revisions:
|
Minor Revisions:
|
||||||
- Improve Deduplication Algorithm with Advanced Option Settings (Implement Chapter & Author-based Comparisons, Resource Intensive)
|
|
||||||
|
|
||||||
- Investigate feasibility of Multi-Page Screenshot (Reader)
|
- Investigate feasibility of Multi-Page Screenshot (Reader)
|
||||||
- Add Hover Info on Library (Make sure doesn't conflict with additional clicks)
|
- Add Hover Info on Library (Make sure doesn't conflict with additional clicks)
|
||||||
- Revise Migration (https://github.com/Suwayomi/Suwayomi-WebUI/pull/1073)
|
- Revise Migration (https://github.com/Suwayomi/Suwayomi-WebUI/pull/1073)
|
||||||
- Look at how Manga are Organized in WebUI and Implement into Series-Detail (Chapter Display is Off)
|
- Look at how Manga are Organized in WebUI and Implement into Series-Detail (Chapter Display is Off)
|
||||||
- Adjustment in Settings for Theme Editor:
|
|
||||||
- Patch Color-Picker to Work Properly
|
|
||||||
- Integrate Download Directory Changes (Settings)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Priority Bugs:
|
Priority Bugs:
|
||||||
- Fix Library Build not Updating
|
- Fix Library-Refresh System (TESTING)
|
||||||
- Loading Buffer for Pictures (Due to Auth Lag)
|
|
||||||
|
|
||||||
|
|
||||||
General/Misc Bugs:
|
General/Misc Bugs:
|
||||||
- Fix Highlightable Elements
|
- Fix Highlightable Elements
|
||||||
@@ -29,18 +22,32 @@ General/Misc Bugs:
|
|||||||
|
|
||||||
In-Progress:
|
In-Progress:
|
||||||
- Enable Cloudflare Bypass (Suwayomi Config) (Requires Patching)
|
- Enable Cloudflare Bypass (Suwayomi Config) (Requires Patching)
|
||||||
|
|
||||||
- Working on 3D Display Cards
|
- Working on 3D Display Cards
|
||||||
|
|
||||||
- Add Small QOL Animations where Appropriate
|
|
||||||
- Search Animations
|
|
||||||
- Card Hover
|
|
||||||
- SeriesDetail Animations
|
|
||||||
- 3D Card
|
|
||||||
|
|
||||||
- Add Flathub Support (Pending Video)
|
- Add Flathub Support (Pending Video)
|
||||||
|
|
||||||
- Currently Moku Migration is on Series. Finish Modularizing then fix rest of files.
|
- QOL Animations & Revamps
|
||||||
|
- Extensions QOL Animations
|
||||||
|
- Folders Slide
|
||||||
|
- Dropdown Formatting (Repositories, Etc)
|
||||||
|
- Extensions Revamps
|
||||||
|
- Notification on Extension Added
|
||||||
|
- Notification on Extension Refresh
|
||||||
|
- Notification on Extension Update
|
||||||
|
- Fix Pill-Shaped Language Filter
|
||||||
|
- Fix ALL ALL EN Tag Issue
|
||||||
|
- Search QOL Animations
|
||||||
|
- Languages Dropdown Animations
|
||||||
|
- Search Revamps
|
||||||
|
- Custom Language Selector Modal
|
||||||
|
- Change Tab Selector to match Extensions & Library Folders (Design)
|
||||||
|
- Filter Genre should Filter Tags as well
|
||||||
|
- Tracking Revamp
|
||||||
|
- Completely Revamp Tracking
|
||||||
|
|
||||||
|
- Fix Search Folder Tabs (Right-Align)
|
||||||
|
|
||||||
|
|
||||||
Testing:
|
Testing Bugs:
|
||||||
|
- Reader Zoom does not work (Dropdown Slider, Value Adjustment); Goes to NaN
|
||||||
|
- Fix Library Folders (Uneven Padding + Bleed into Other Folders); Appears Constraints are Off
|
||||||
|
-
|
||||||
@@ -1,3 +1,15 @@
|
|||||||
|
export const GET_RECENTLY_UPDATED = `
|
||||||
|
query GetRecentlyUpdated {
|
||||||
|
chapters(orderBy: FETCHED_AT, orderByType: DESC, first: 300) {
|
||||||
|
nodes {
|
||||||
|
mangaId
|
||||||
|
fetchedAt
|
||||||
|
manga { id title thumbnailUrl inLibrary }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
export const GET_CHAPTERS = `
|
export const GET_CHAPTERS = `
|
||||||
query GetChapters($mangaId: Int!) {
|
query GetChapters($mangaId: Int!) {
|
||||||
chapters(condition: { mangaId: $mangaId }) {
|
chapters(condition: { mangaId: $mangaId }) {
|
||||||
@@ -7,4 +19,4 @@ export const GET_CHAPTERS = `
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@@ -5,12 +5,13 @@
|
|||||||
import {
|
import {
|
||||||
GET_CATEGORIES, GET_LIBRARY, UPDATE_MANGA, UPDATE_MANGAS,
|
GET_CATEGORIES, GET_LIBRARY, UPDATE_MANGA, UPDATE_MANGAS,
|
||||||
GET_CHAPTERS, DELETE_DOWNLOADED_CHAPTERS, DEQUEUE_DOWNLOAD,
|
GET_CHAPTERS, DELETE_DOWNLOADED_CHAPTERS, DEQUEUE_DOWNLOAD,
|
||||||
CREATE_CATEGORY, UPDATE_MANGA_CATEGORIES, UPDATE_LIBRARY, LIBRARY_UPDATE_STATUS,
|
CREATE_CATEGORY, UPDATE_MANGA_CATEGORIES,
|
||||||
UPDATE_CATEGORY_ORDER,
|
UPDATE_CATEGORY_ORDER,
|
||||||
} from "@api";
|
} from "@api";
|
||||||
import { cache, CACHE_KEYS, CACHE_GROUPS, DEFAULT_TTL_MS } from "@core/cache/queryCache";
|
import { cache, CACHE_KEYS, CACHE_GROUPS, DEFAULT_TTL_MS } from "@core/cache/queryCache";
|
||||||
import { dedupeMangaById, dedupeMangaByTitle, shouldHideNsfw } from "@core/util";
|
import { dedupeMangaById, dedupeMangaByTitle, shouldHideNsfw } from "@core/util";
|
||||||
import { sortLibrary } from "../lib/librarySort";
|
import { sortLibrary } from "../lib/librarySort";
|
||||||
|
import { startLibraryUpdate } from "../lib/libraryUpdater";
|
||||||
import { createPaginator } from "@core/algorithms/paginate";
|
import { createPaginator } from "@core/algorithms/paginate";
|
||||||
import {
|
import {
|
||||||
store, setCategories, setLibraryUpdates, addToast,
|
store, setCategories, setLibraryUpdates, addToast,
|
||||||
@@ -57,7 +58,7 @@
|
|||||||
|
|
||||||
let refreshing: boolean = $state(false);
|
let refreshing: boolean = $state(false);
|
||||||
let refreshProgress = $state({ finished: 0, total: 0 });
|
let refreshProgress = $state({ finished: 0, total: 0 });
|
||||||
let pollTimer: ReturnType<typeof setTimeout> | null = null;
|
let cancelUpdate: (() => void) | null = null;
|
||||||
let refreshDone: boolean = $state(false);
|
let refreshDone: boolean = $state(false);
|
||||||
let refreshDoneTimer: ReturnType<typeof setTimeout> | null = null;
|
let refreshDoneTimer: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
|
||||||
@@ -378,47 +379,27 @@
|
|||||||
if (refreshing) return;
|
if (refreshing) return;
|
||||||
refreshing = true;
|
refreshing = true;
|
||||||
refreshProgress = { finished: 0, total: 0 };
|
refreshProgress = { finished: 0, total: 0 };
|
||||||
const prevCounts = new Map(allManga.map(m => [m.id, m.unreadCount ?? 0]));
|
|
||||||
let seenWork = false;
|
|
||||||
|
|
||||||
try {
|
cancelUpdate = startLibraryUpdate({
|
||||||
const res = await gql<{ updateLibrary: { updateStatus: { jobsInfo: { isRunning: boolean; totalJobs: number } } } }>(UPDATE_LIBRARY, {});
|
onProgress(p) {
|
||||||
seenWork = res.updateLibrary.updateStatus.jobsInfo.totalJobs > 0;
|
refreshProgress = p;
|
||||||
} catch { refreshing = false; return; }
|
},
|
||||||
|
async onDone({ entries, totalUpdated, newChapters }) {
|
||||||
pollTimer = setTimeout(function poll() {
|
refreshing = false;
|
||||||
gql<{ libraryUpdateStatus: {
|
cancelUpdate = null;
|
||||||
jobsInfo: { isRunning: boolean; finishedJobs: number; totalJobs: number };
|
setLibraryUpdates(entries);
|
||||||
mangaUpdates: { status: string; manga: { id: number; title: string; thumbnailUrl: string; unreadCount: number } }[];
|
cache.clearGroup(CACHE_GROUPS.LIBRARY);
|
||||||
} }>(LIBRARY_UPDATE_STATUS, {})
|
await loadData();
|
||||||
.then(d => {
|
refreshDone = true;
|
||||||
const { jobsInfo, mangaUpdates } = d.libraryUpdateStatus;
|
if (refreshDoneTimer) clearTimeout(refreshDoneTimer);
|
||||||
refreshProgress = { finished: jobsInfo.finishedJobs, total: jobsInfo.totalJobs };
|
refreshDoneTimer = setTimeout(() => { refreshDone = false; }, 2500);
|
||||||
if (jobsInfo.totalJobs > 0) seenWork = true;
|
showToast(newChapters, totalUpdated);
|
||||||
|
},
|
||||||
if (!jobsInfo.isRunning && seenWork) {
|
onError() {
|
||||||
refreshing = false;
|
refreshing = false;
|
||||||
pollTimer = null;
|
cancelUpdate = null;
|
||||||
const entries: LibraryUpdateEntry[] = mangaUpdates
|
},
|
||||||
.filter(u => u.status === "FINISHED")
|
});
|
||||||
.reduce<LibraryUpdateEntry[]>((acc, u) => {
|
|
||||||
const newChapters = Math.max(0, (u.manga.unreadCount ?? 0) - (prevCounts.get(u.manga.id) ?? 0));
|
|
||||||
if (newChapters > 0) acc.push({ mangaId: u.manga.id, mangaTitle: u.manga.title, thumbnailUrl: u.manga.thumbnailUrl, newChapters, checkedAt: Date.now() });
|
|
||||||
return acc;
|
|
||||||
}, []);
|
|
||||||
setLibraryUpdates(entries);
|
|
||||||
cache.clearGroup(CACHE_GROUPS.LIBRARY);
|
|
||||||
loadData();
|
|
||||||
refreshDone = true;
|
|
||||||
if (refreshDoneTimer) clearTimeout(refreshDoneTimer);
|
|
||||||
refreshDoneTimer = setTimeout(() => { refreshDone = false; }, 2500);
|
|
||||||
showToast(entries.reduce((s, e) => s + e.newChapters, 0), entries.length);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
pollTimer = setTimeout(poll, 3000);
|
|
||||||
})
|
|
||||||
.catch(() => { refreshing = false; pollTimer = null; });
|
|
||||||
}, 2000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onTabDragStart(e: DragEvent, cat: Category) {
|
function onTabDragStart(e: DragEvent, cat: Category) {
|
||||||
@@ -482,7 +463,7 @@
|
|||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
ro.disconnect(); unsub();
|
ro.disconnect(); unsub();
|
||||||
if (pollTimer) clearTimeout(pollTimer);
|
cancelUpdate?.();
|
||||||
window.removeEventListener("keydown", onKeyDown);
|
window.removeEventListener("keydown", onKeyDown);
|
||||||
document.removeEventListener("mousedown", onDocMouseDown, true);
|
document.removeEventListener("mousedown", onDocMouseDown, true);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
import { gql } from "@api/client";
|
||||||
|
import { LIBRARY_UPDATE_STATUS } from "@api/queries/manga";
|
||||||
|
import { UPDATE_LIBRARY } from "@api/mutations/manga";
|
||||||
|
import { GET_RECENTLY_UPDATED } from "@api/queries/chapters";
|
||||||
|
import type { LibraryUpdateEntry } from "@store/state.svelte";
|
||||||
|
|
||||||
|
const POLL_INTERVAL_MS = 3000;
|
||||||
|
const POLL_INITIAL_MS = 2000;
|
||||||
|
|
||||||
|
export interface UpdateProgress {
|
||||||
|
finished: number;
|
||||||
|
total: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateResult {
|
||||||
|
entries: LibraryUpdateEntry[];
|
||||||
|
totalUpdated: number;
|
||||||
|
newChapters: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LibraryUpdaterCallbacks {
|
||||||
|
onProgress: (p: UpdateProgress) => void;
|
||||||
|
onDone: (r: UpdateResult) => void;
|
||||||
|
onError: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function startLibraryUpdate(callbacks: LibraryUpdaterCallbacks): () => void {
|
||||||
|
let timer: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
let cancelled = false;
|
||||||
|
const startedAt = Math.floor(Date.now() / 1000);
|
||||||
|
|
||||||
|
function cancel() {
|
||||||
|
cancelled = true;
|
||||||
|
if (timer) { clearTimeout(timer); timer = null; }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function run() {
|
||||||
|
let seenWork = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await gql<{
|
||||||
|
updateLibrary: { updateStatus: { jobsInfo: { isRunning: boolean; totalJobs: number } } }
|
||||||
|
}>(UPDATE_LIBRARY, {});
|
||||||
|
if (cancelled) return;
|
||||||
|
seenWork = res.updateLibrary.updateStatus.jobsInfo.totalJobs > 0;
|
||||||
|
} catch {
|
||||||
|
if (!cancelled) callbacks.onError();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function poll() {
|
||||||
|
gql<{
|
||||||
|
libraryUpdateStatus: {
|
||||||
|
jobsInfo: { isRunning: boolean; finishedJobs: number; totalJobs: number };
|
||||||
|
mangaUpdates: { status: string; manga: { id: number } }[];
|
||||||
|
}
|
||||||
|
}>(LIBRARY_UPDATE_STATUS, {})
|
||||||
|
.then(async d => {
|
||||||
|
if (cancelled) return;
|
||||||
|
const { jobsInfo } = d.libraryUpdateStatus;
|
||||||
|
|
||||||
|
if (jobsInfo.totalJobs > 0) seenWork = true;
|
||||||
|
callbacks.onProgress({ finished: jobsInfo.finishedJobs, total: jobsInfo.totalJobs });
|
||||||
|
|
||||||
|
if (!jobsInfo.isRunning && seenWork) {
|
||||||
|
const recent = await gql<{
|
||||||
|
chapters: { nodes: { mangaId: number; mangaTitle: string; thumbnailUrl: string; fetchedAt: string }[] }
|
||||||
|
}>(GET_RECENTLY_UPDATED, {}).catch(() => ({ chapters: { nodes: [] } }));
|
||||||
|
|
||||||
|
if (cancelled) return;
|
||||||
|
|
||||||
|
const byManga = new Map<number, LibraryUpdateEntry>();
|
||||||
|
for (const ch of recent.chapters.nodes) {
|
||||||
|
if (!ch.manga.inLibrary) continue;
|
||||||
|
if (Number(ch.fetchedAt) < startedAt) continue;
|
||||||
|
const existing = byManga.get(ch.mangaId);
|
||||||
|
if (existing) {
|
||||||
|
existing.newChapters++;
|
||||||
|
} else {
|
||||||
|
byManga.set(ch.mangaId, {
|
||||||
|
mangaId: ch.mangaId,
|
||||||
|
mangaTitle: ch.mangaTitle,
|
||||||
|
thumbnailUrl: ch.thumbnailUrl,
|
||||||
|
newChapters: 1,
|
||||||
|
checkedAt: Date.now(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const entries = [...byManga.values()];
|
||||||
|
const newChapters = entries.reduce((s, e) => s + e.newChapters, 0);
|
||||||
|
|
||||||
|
callbacks.onDone({ entries, totalUpdated: entries.length, newChapters });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
timer = setTimeout(poll, POLL_INTERVAL_MS);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
if (!cancelled) callbacks.onError();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
timer = setTimeout(poll, POLL_INITIAL_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
run();
|
||||||
|
return cancel;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user