diff --git a/src/state/watched/store.ts b/src/state/watched/store.ts index 075e9e43..4bab3c78 100644 --- a/src/state/watched/store.ts +++ b/src/state/watched/store.ts @@ -1,7 +1,35 @@ +import { DetailedMeta, getMetaFromId } from "@/backend/metadata/getmeta"; +import { searchForMedia } from "@/backend/metadata/search"; +import { MWMediaMeta, MWMediaType } from "@/backend/metadata/types"; import { versionedStoreBuilder } from "@/utils/storage"; +import { WatchedStoreData, WatchedStoreItem } from "./context"; + +interface OldMediaBase { + mediaId: number; + mediaType: MWMediaType; + percentage: number; + progress: number; + providerId: string; + title: string; + year: number; +} + +interface OldMovie extends OldMediaBase { + mediaType: MWMediaType.MOVIE; +} + +interface OldSeries extends OldMediaBase { + mediaType: MWMediaType.SERIES; + episodeId: number; + seasonId: number; +} + +interface OldData { + items: (OldMovie | OldSeries)[]; +} export const VideoProgressStore = versionedStoreBuilder() - .setKey("video-progress-v3") + .setKey("video-progress") .addVersion({ version: 0, }) @@ -15,7 +43,11 @@ export const VideoProgressStore = versionedStoreBuilder() }) .addVersion({ version: 2, - migrate() { + migrate(old: OldData) { + requestAnimationFrame(() => { + // eslint-disable-next-line no-use-before-define + migrateV2(old); + }); return { items: [], }; @@ -27,3 +59,153 @@ export const VideoProgressStore = versionedStoreBuilder() }, }) .build(); + +async function migrateV2(old: OldData) { + const oldData = old; + if (!oldData) return; + + const uniqueMedias: Record = {}; + oldData.items.forEach((item: any) => { + if (uniqueMedias[item.mediaId]) return; + uniqueMedias[item.mediaId] = item; + }); + + const yearsAreClose = (a: number, b: number) => { + return Math.abs(a - b) <= 1; + }; + + const mediaMetas: Record> = {}; + + const relevantItems = await Promise.all( + Object.values(uniqueMedias).map(async (item) => { + const year = Number(item.year.toString().split("-")[0]); + const data = await searchForMedia({ + searchQuery: `${item.title} ${year}`, + type: item.mediaType, + }); + const relevantItem = data.find((res) => + yearsAreClose(Number(res.year), year) + ); + if (!relevantItem) { + console.error("No item"); + return; + } + return { + id: item.mediaId, + data: relevantItem, + }; + }) + ); + + for (const item of relevantItems.filter(Boolean)) { + if (!item) continue; + + let keys: (string | null)[][] = [["0", "0"]]; + if (item.data.type === "series") { + const meta = await getMetaFromId(item.data.type, item.data.id); + if (!meta || !meta?.meta.seasons) return; + const seasonNumbers = [ + ...new Set( + oldData.items + .filter((watchedEntry: any) => watchedEntry.mediaId === item.id) + .map((watchedEntry: any) => watchedEntry.seasonId) + ), + ]; + const seasons = seasonNumbers + .map((num) => ({ + num, + season: meta.meta?.seasons?.[(num as number) - 1], + })) + .filter(Boolean); + keys = seasons + .map((season) => (season ? [season.num, season?.season?.id] : [])) + .filter((entry) => entry.length > 0); // Stupid TypeScript + } + + if (!mediaMetas[item.id]) mediaMetas[item.id] = {}; + await Promise.all( + keys.map(async ([key, id]) => { + if (!key) return; + mediaMetas[item.id][key] = await getMetaFromId( + item.data.type, + item.data.id, + id === "0" || id === null ? undefined : id + ); + }) + ); + } + + // We've got all the metadata you can dream of now + // Now let's convert stuff into the new format. + interface WatchedStoreDataWithVersion extends WatchedStoreData { + "--version": number; + } + const newData: WatchedStoreDataWithVersion = { + ...oldData, + items: [], + "--version": 2, + }; + + for (const oldWatched of oldData.items) { + if (oldWatched.mediaType === "movie") { + if (!mediaMetas[oldWatched.mediaId]["0"]?.meta) continue; + + const newItem: WatchedStoreItem = { + item: { + meta: mediaMetas[oldWatched.mediaId]["0"]?.meta as MWMediaMeta, + }, + progress: oldWatched.progress, + percentage: oldWatched.percentage, + watchedAt: Date.now(), // There was no watchedAt in V2 + }; + + oldData.items = oldData.items.filter( + (item) => JSON.stringify(item) !== JSON.stringify(oldWatched) + ); + newData.items.push(newItem); + } else if (oldWatched.mediaType === "series") { + if (!mediaMetas[oldWatched.mediaId][oldWatched.seasonId]?.meta) continue; + + const meta = mediaMetas[oldWatched.mediaId][oldWatched.seasonId] + ?.meta as MWMediaMeta; + + if (meta.type !== "series") return; + + const newItem: WatchedStoreItem = { + item: { + meta, + series: { + episode: Number(oldWatched.episodeId), + season: Number(oldWatched.seasonId), + seasonId: meta.seasonData.id, + episodeId: + meta.seasonData.episodes[Number(oldWatched.episodeId) - 1].id, + }, + }, + progress: oldWatched.progress, + percentage: oldWatched.percentage, + watchedAt: Date.now(), // There was no watchedAt in V2 + }; + + if ( + newData.items.find( + (item) => + item.item.meta.id === newItem.item.meta.id && + item.item.series?.episodeId === newItem.item.series?.episodeId + ) + ) + continue; + + oldData.items = oldData.items.filter( + (item) => JSON.stringify(item) !== JSON.stringify(oldWatched) + ); + newData.items.push(newItem); + } + } + + console.log(JSON.stringify(old), JSON.stringify(newData)); + if (JSON.stringify(old.items) !== JSON.stringify(newData.items)) { + console.log(newData); + VideoProgressStore.get().save(newData); + } +} diff --git a/src/views/search/HomeView.tsx b/src/views/search/HomeView.tsx index d07bdd1f..b5a17843 100644 --- a/src/views/search/HomeView.tsx +++ b/src/views/search/HomeView.tsx @@ -6,19 +6,11 @@ import { getIfBookmarkedFromPortable, useBookmarkContext, } from "@/state/bookmark"; -import { - useWatchedContext, - WatchedStoreData, - WatchedStoreItem, -} from "@/state/watched"; +import { useWatchedContext } from "@/state/watched"; import { WatchedMediaCard } from "@/components/media/WatchedMediaCard"; import { EditButton } from "@/components/buttons/EditButton"; -import { useCallback, useState } from "react"; +import { useState } from "react"; import { useAutoAnimate } from "@formkit/auto-animate/react"; -import { VideoProgressStore } from "@/state/watched/store"; -import { searchForMedia } from "@/backend/metadata/search"; -import { DetailedMeta, getMetaFromId } from "@/backend/metadata/getmeta"; -import { MWMediaMeta, MWMediaType } from "@/backend/metadata/types"; function Bookmarks() { const { t } = useTranslation(); @@ -51,30 +43,6 @@ function Bookmarks() { ); } -interface OldMediaBase { - mediaId: number; - mediaType: MWMediaType; - percentage: number; - progress: number; - providerId: string; - title: string; - year: number; -} - -interface OldMovie extends OldMediaBase { - mediaType: MWMediaType.MOVIE; -} - -interface OldSeries extends OldMediaBase { - mediaType: MWMediaType.SERIES; - episodeId: number; - seasonId: number; -} - -interface OldData { - items: (OldMovie | OldSeries)[]; -} - function Watched() { const { t } = useTranslation(); const { getFilteredBookmarks } = useBookmarkContext(); @@ -87,189 +55,6 @@ function Watched() { (v) => !getIfBookmarkedFromPortable(bookmarks, v.item.meta) ); - /* - AAA - */ - const watchedLocalstorage = VideoProgressStore.get(); - const [watched, setWatchedReal] = useState( - watchedLocalstorage as WatchedStoreData - ); - - const setWatched = useCallback( - (data: any) => { - setWatchedReal((old) => { - let newData = data; - if (data.constructor === Function) { - newData = data(old); - } - watchedLocalstorage.save(newData); - return newData; - }); - }, - [setWatchedReal, watchedLocalstorage] - ); - - (async () => { - const oldData: OldData | null = localStorage.getItem("video-progress") - ? JSON.parse(localStorage.getItem("video-progress") || "") - : null; - - if (!oldData) return; - - const uniqueMedias: Record = {}; - oldData.items.forEach((item: any) => { - if (uniqueMedias[item.mediaId]) return; - uniqueMedias[item.mediaId] = item; - }); - - const yearsAreClose = (a: number, b: number) => { - return Math.abs(a - b) <= 1; - }; - - const mediaMetas: Record> = {}; - - Promise.all( - Object.values(uniqueMedias).map(async (item) => { - const year = Number(item.year.toString().split("-")[0]); - const data = await searchForMedia({ - searchQuery: `${item.title} ${year}`, - type: item.mediaType, - }); - const relevantItem = data.find((res) => - yearsAreClose(Number(res.year), year) - ); - if (!relevantItem) { - console.error("No item"); - return; - } - return { - id: item.mediaId, - data: relevantItem, - }; - }) - ).then(async (relevantItems) => { - console.log(relevantItems); - for (const item of relevantItems.filter(Boolean)) { - if (!item) continue; - - let keys: (string | null)[][] = [["0", "0"]]; - if (item.data.type === "series") { - const meta = await getMetaFromId(item.data.type, item.data.id); - if (!meta || !meta?.meta.seasons) return; - const seasonNumbers = [ - ...new Set( - oldData.items - .filter((watchedEntry: any) => watchedEntry.mediaId === item.id) - .map((watchedEntry: any) => watchedEntry.seasonId) - ), - ]; - const seasons = seasonNumbers - .map((num) => ({ - num, - season: meta.meta?.seasons?.[(num as number) - 1], - })) - .filter(Boolean); - keys = seasons - .map((season) => (season ? [season.num, season?.season?.id] : [])) - .filter((entry) => entry.length > 0); // Stupid TypeScript - } - - if (!mediaMetas[item.id]) mediaMetas[item.id] = {}; - await Promise.all( - keys.map(async ([key, id]) => { - if (!key) return; - mediaMetas[item.id][key] = await getMetaFromId( - item.data.type, - item.data.id, - id === "0" || id === null ? undefined : id - ); - }) - ); - } - - // We've got all the metadata you can dream of now - // Now let's convert stuff into the new format. - const newData: WatchedStoreData = JSON.parse(JSON.stringify(watched)); - - for (const oldWatched of oldData.items) { - if (oldWatched.mediaType === "movie") { - if (!mediaMetas[oldWatched.mediaId]["0"]?.meta) continue; - - const newItem: WatchedStoreItem = { - item: { - meta: mediaMetas[oldWatched.mediaId]["0"]?.meta as MWMediaMeta, - }, - progress: oldWatched.progress, - percentage: oldWatched.percentage, - watchedAt: Date.now(), // There was no watchedAt in V2 - }; - if ( - newData.items.find( - (item) => item.item.meta.id === newItem.item.meta.id - ) - ) - continue; - - oldData.items = oldData.items.filter( - (item) => JSON.stringify(item) !== JSON.stringify(oldWatched) - ); - newData.items.push(newItem); - } else if (oldWatched.mediaType === "series") { - // console.log(oldWatched); - // console.log(mediaMetas[oldWatched.mediaId][oldWatched.seasonId]); - - if (!mediaMetas[oldWatched.mediaId][oldWatched.seasonId]?.meta) - continue; - - const meta = mediaMetas[oldWatched.mediaId][oldWatched.seasonId] - ?.meta as MWMediaMeta; - - if (meta.type !== "series") return; - - // console.log(meta.seasonData); - const newItem: WatchedStoreItem = { - item: { - meta, - series: { - episode: Number(oldWatched.episodeId), - season: Number(oldWatched.seasonId), - seasonId: meta.seasonData.id, - episodeId: - meta.seasonData.episodes[Number(oldWatched.episodeId) - 1].id, - }, - }, - progress: oldWatched.progress, - percentage: oldWatched.percentage, - watchedAt: Date.now(), // There was no watchedAt in V2 - }; - - if ( - newData.items.find( - (item) => - item.item.meta.id === newItem.item.meta.id && - item.item.series?.episodeId === newItem.item.series?.episodeId - ) - ) - continue; - - oldData.items = oldData.items.filter( - (item) => JSON.stringify(item) !== JSON.stringify(oldWatched) - ); - newData.items.push(newItem); - } - } - - if (JSON.stringify(newData) !== JSON.stringify(watched)) { - localStorage.setItem("video-progress", JSON.stringify(oldData)); - setWatched(() => newData); - } - }); - })(); - - /* - AAA - */ - if (watchedItems.length === 0) return null; return (