diff options
Diffstat (limited to 'src/lib/Data')
| -rw-r--r-- | src/lib/Data/AniList/cacheHydration.ts | 48 | ||||
| -rw-r--r-- | src/lib/Data/AniList/media.ts | 114 |
2 files changed, 112 insertions, 50 deletions
diff --git a/src/lib/Data/AniList/cacheHydration.ts b/src/lib/Data/AniList/cacheHydration.ts new file mode 100644 index 00000000..434d2a82 --- /dev/null +++ b/src/lib/Data/AniList/cacheHydration.ts @@ -0,0 +1,48 @@ +import { browser } from "$app/environment"; +import anime from "$stores/anime"; +import lastPruneTimes from "$stores/lastPruneTimes"; +import manga from "$stores/manga"; +import localforage from "localforage"; + +type MediaCacheKind = "anime" | "manga"; + +interface StoredLastPruneTimes { + anime: number; + chapters: number; + manga: number; +} + +const hydration = new Map<MediaCacheKind, Promise<void>>(); + +const isStoredLastPruneTimes = ( + value: unknown, +): value is StoredLastPruneTimes => + typeof value === "object" && + value !== null && + typeof (value as StoredLastPruneTimes).anime === "number" && + typeof (value as StoredLastPruneTimes).chapters === "number" && + typeof (value as StoredLastPruneTimes).manga === "number"; + +export const hydrateMediaListCache = (kind: MediaCacheKind) => { + if (!browser) return Promise.resolve(); + + const existing = hydration.get(kind); + + if (existing) return existing; + + const promise = (async () => { + const [cache, pruneTimes] = await Promise.all([ + localforage.getItem<string>(kind), + localforage.getItem<StoredLastPruneTimes>("lastPruneTimes"), + ]); + + if (typeof cache === "string" && cache.length) + (kind === "anime" ? anime : manga).set(cache); + + if (isStoredLastPruneTimes(pruneTimes)) lastPruneTimes.set(pruneTimes); + })(); + + hydration.set(kind, promise); + + return promise; +}; diff --git a/src/lib/Data/AniList/media.ts b/src/lib/Data/AniList/media.ts index f2a4aad6..d8315b19 100644 --- a/src/lib/Data/AniList/media.ts +++ b/src/lib/Data/AniList/media.ts @@ -233,6 +233,21 @@ const assignDefaultOptions = (options: CollectionOptions) => { return nonNullOptions; }; +const inFlightCollections = new Map<string, Promise<Media[]>>(); + +const collectionKey = ( + type: Type, + userId: number, + options: CollectionOptions, +) => + JSON.stringify({ + type, + userId, + includeCompleted: options.includeCompleted, + all: options.all, + includeRelations: options.includeRelations, + }); + export const mediaListCollection = async ( anilistAuthorisation: AniListAuthorisation, userIdentity: UserIdentity, @@ -274,61 +289,60 @@ export const mediaListCollection = async ( if (mediaCache !== undefined && mediaCache !== "") return parseJsonStringOrDefault<Media[]>(mediaCache, []); - const userIdResponse = await ( - await fetch("https://graphql.anilist.co", { - method: "POST", - headers: { - Authorization: `${anilistAuthorisation.tokenType} ${anilistAuthorisation.accessToken}`, - "Content-Type": "application/json", - Accept: "application/json", - }, - body: JSON.stringify({ - query: collectionQueryTemplate(type, userIdentity.id, options), - }), - }) - ).json(); + const key = collectionKey(type, userIdentity.id, options); + const existing = inFlightCollections.get(key); - if ( - !userIdResponse["data"] || - !userIdResponse["data"]["MediaListCollection"] || - !userIdResponse["data"]["MediaListCollection"]["lists"] - ) - return []; + if (existing) return existing; - if (mediaCache === "") - if (type === Type.Anime) - anime.set( - JSON.stringify( - flattenLists( - userIdResponse["data"]["MediaListCollection"]["lists"], - options.all, - ), - ), - ); - else - manga.set( - JSON.stringify( - flattenLists( - userIdResponse["data"]["MediaListCollection"]["lists"], - options.all, - ), - ), - ); + const request = (async () => { + const userIdResponse = await ( + await fetch("https://graphql.anilist.co", { + method: "POST", + headers: { + Authorization: `${anilistAuthorisation.tokenType} ${anilistAuthorisation.accessToken}`, + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify({ + query: collectionQueryTemplate(type, userIdentity.id, options), + }), + }) + ).json(); - if (options.addNotification) - options.addNotification( - getOptions({ - heading: options.notificationType - ? options.notificationType - : Type[type], - description: "Re-cached media lists from AniList", - }), + if ( + !userIdResponse["data"] || + !userIdResponse["data"]["MediaListCollection"] || + !userIdResponse["data"]["MediaListCollection"]["lists"] + ) + return []; + + const flattened = flattenLists( + userIdResponse["data"]["MediaListCollection"]["lists"], + options.all, ); - return flattenLists( - userIdResponse["data"]["MediaListCollection"]["lists"], - options.all, - ); + if (mediaCache === "") + if (type === Type.Anime) anime.set(JSON.stringify(flattened)); + else manga.set(JSON.stringify(flattened)); + + if (options.addNotification) + options.addNotification( + getOptions({ + heading: options.notificationType + ? options.notificationType + : Type[type], + description: "Re-cached media lists from AniList", + }), + ); + + return flattened; + })().finally(() => { + inFlightCollections.delete(key); + }); + + inFlightCollections.set(key, request); + + return request; }; export const publicMediaListCollection = async ( |