From c4c7816543900173e17bde50826bd4f397ae21d6 Mon Sep 17 00:00:00 2001 From: mrjvs Date: Thu, 22 Jun 2023 22:37:16 +0200 Subject: [PATCH] migrations but better Co-authored-by: William Oldham --- src/backend/metadata/tmdb.ts | 29 ++++--- src/backend/providers/gomovies.ts | 2 +- src/backend/providers/superstream/index.ts | 2 +- src/state/bookmark/store.ts | 2 +- src/state/watched/migrations/v3.ts | 92 ++++++++++++---------- src/state/watched/store.ts | 2 +- src/utils/storage.ts | 7 +- src/utils/typeguard.ts | 3 + 8 files changed, 79 insertions(+), 60 deletions(-) create mode 100644 src/utils/typeguard.ts diff --git a/src/backend/metadata/tmdb.ts b/src/backend/metadata/tmdb.ts index db665528..1c442028 100644 --- a/src/backend/metadata/tmdb.ts +++ b/src/backend/metadata/tmdb.ts @@ -143,21 +143,24 @@ export async function searchMedia( return data; } -export async function getMediaDetails(id: string, type: TMDBContentTypes) { - let data; +// Conditional type which for inferring the return type based on the content type +type MediaDetailReturn = T extends "movie" + ? TMDBMovieData + : T extends "show" + ? TMDBShowData + : never; - switch (type) { - case "movie": - data = await get(`/movie/${id}`); - break; - case "show": - data = await get(`/tv/${id}`); - break; - default: - throw new Error("Invalid media type"); +export function getMediaDetails< + T extends TMDBContentTypes, + TReturn = MediaDetailReturn +>(id: string, type: T): Promise { + if (type === "movie") { + return get(`/movie/${id}`); } - - return data; + if (type === "show") { + return get(`/tv/${id}`); + } + throw new Error("Invalid media type"); } export function getMediaPoster(posterPath: string | null): string | undefined { diff --git a/src/backend/providers/gomovies.ts b/src/backend/providers/gomovies.ts index fdce289b..ddd43509 100644 --- a/src/backend/providers/gomovies.ts +++ b/src/backend/providers/gomovies.ts @@ -8,7 +8,7 @@ const gomoviesBase = "https://gomovies.sx"; registerProvider({ id: "gomovies", displayName: "GOmovies", - rank: 300, + rank: 200, type: [MWMediaType.MOVIE, MWMediaType.SERIES], async scrape({ media, episode }) { diff --git a/src/backend/providers/superstream/index.ts b/src/backend/providers/superstream/index.ts index 75a8b844..5af85cb9 100644 --- a/src/backend/providers/superstream/index.ts +++ b/src/backend/providers/superstream/index.ts @@ -142,7 +142,7 @@ const convertSubtitles = (subtitleGroup: any): MWCaption | null => { registerProvider({ id: "superstream", displayName: "Superstream", - rank: 200, + rank: 300, type: [MWMediaType.MOVIE, MWMediaType.SERIES], async scrape({ media, episode, progress }) { diff --git a/src/state/bookmark/store.ts b/src/state/bookmark/store.ts index 51de0ed0..b2020020 100644 --- a/src/state/bookmark/store.ts +++ b/src/state/bookmark/store.ts @@ -14,7 +14,7 @@ export const BookmarkStore = createVersionedStore() }) .addVersion({ version: 1, - migrate(old: OldBookmarks) { + migrate(old: BookmarkStoreData) { return migrateV2Bookmarks(old); }, }) diff --git a/src/state/watched/migrations/v3.ts b/src/state/watched/migrations/v3.ts index 71e0b182..dffae637 100644 --- a/src/state/watched/migrations/v3.ts +++ b/src/state/watched/migrations/v3.ts @@ -1,14 +1,20 @@ import { getLegacyMetaFromId } from "@/backend/metadata/getmeta"; -import { getMovieFromExternalId } from "@/backend/metadata/tmdb"; +import { + getEpisodes, + getMediaDetails, + getMovieFromExternalId, +} from "@/backend/metadata/tmdb"; import { MWMediaType } from "@/backend/metadata/types/mw"; +import { BookmarkStoreData } from "@/state/bookmark/types"; +import { isNotNull } from "@/utils/typeguard"; import { WatchedStoreData } from "../types"; async function migrateId( - id: number, + id: string, type: MWMediaType ): Promise { - const meta = await getLegacyMetaFromId(type, id.toString()); + const meta = await getLegacyMetaFromId(type, id); if (!meta) return undefined; const { tmdbId, imdbId } = meta; @@ -25,57 +31,59 @@ async function migrateId( } } -export async function migrateV2Bookmarks(old: any) { - const oldData = old; - if (!oldData) return; - - const updatedBookmarks = oldData.bookmarks.map( - async (item: { id: number; type: MWMediaType }) => ({ - ...item, - id: await migrateId(item.id, item.type), - }) - ); +export async function migrateV2Bookmarks(old: BookmarkStoreData) { + const updatedBookmarks = old.bookmarks.map(async (item) => ({ + ...item, + id: await migrateId(item.id, item.type).catch(() => undefined), + })); return { bookmarks: (await Promise.all(updatedBookmarks)).filter((item) => item.id), }; } -export async function migrateV3Videos(old: any) { - const oldData = old; - if (!oldData) return; - +export async function migrateV3Videos( + old: WatchedStoreData +): Promise { const updatedItems = await Promise.all( - oldData.items.map(async (item: any) => { - const migratedId = await migrateId( - item.item.meta.id, - item.item.meta.type - ); + old.items.map(async (progress) => { + try { + const migratedId = await migrateId( + progress.item.meta.id, + progress.item.meta.type + ); - const migratedItem = { - ...item, - item: { - ...item.item, - meta: { - ...item.item.meta, - id: migratedId, - }, - }, - }; + if (!migratedId) return null; - return { - ...item, - item: migratedId ? migratedItem : item.item, - }; + const clone = structuredClone(progress); + clone.item.meta.id = migratedId; + if (clone.item.series) { + const series = clone.item.series; + const details = await getMediaDetails(migratedId, "show"); + + const season = details.seasons.find( + (v) => v.season_number === series.season + ); + if (!season) return null; + + const episodes = await getEpisodes(migratedId, season.season_number); + const episode = episodes.find( + (v) => v.episode_number === series.episode + ); + if (!episode) return null; + + clone.item.series.episodeId = episode.id.toString(); + clone.item.series.seasonId = season.id.toString(); + } + + return clone; + } catch (err) { + return null; + } }) ); - const newData: WatchedStoreData = { - items: updatedItems.map((item) => item.item), - }; - return { - ...oldData, - items: newData.items, + items: updatedItems.filter(isNotNull), }; } diff --git a/src/state/watched/store.ts b/src/state/watched/store.ts index b59c37dc..c11e3f59 100644 --- a/src/state/watched/store.ts +++ b/src/state/watched/store.ts @@ -22,7 +22,7 @@ export const VideoProgressStore = createVersionedStore() }) .addVersion({ version: 2, - migrate(old: OldData) { + migrate(old: WatchedStoreData) { return migrateV3Videos(old); }, }) diff --git a/src/utils/storage.ts b/src/utils/storage.ts index f48e0245..83057d54 100644 --- a/src/utils/storage.ts +++ b/src/utils/storage.ts @@ -46,8 +46,13 @@ export async function initializeStores() { let mostRecentData = data; try { for (const version of relevantVersions) { - if (version.migrate) + if (version.migrate) { + localStorage.setItem( + `BACKUP-v${version.version}-${internal.key}`, + JSON.stringify(mostRecentData) + ); mostRecentData = await version.migrate(mostRecentData); + } } } catch (err) { console.error(`FAILED TO MIGRATE STORE ${internal.key}`, err); diff --git a/src/utils/typeguard.ts b/src/utils/typeguard.ts new file mode 100644 index 00000000..95dd81a1 --- /dev/null +++ b/src/utils/typeguard.ts @@ -0,0 +1,3 @@ +export function isNotNull(obj: T | null): obj is T { + return obj != null; +}