Move migration out of home into store

This commit is contained in:
Jip Fr 2023-02-11 01:05:27 +01:00
parent 8f23240ea1
commit dd14b575eb
2 changed files with 186 additions and 219 deletions

View File

@ -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<string, any> = {};
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<string, Record<string, DetailedMeta | null>> = {};
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);
}
}

View File

@ -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<WatchedStoreData>(
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<string, any> = {};
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<string, Record<string, DetailedMeta | null>> = {};
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 (