summaryrefslogtreecommitdiff
path: root/apps/web/src/services/storage/migrations/v1-to-v2.ts
blob: ef38154114f199082c8e44a8bd60c3e58e818811 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
import {
	IndexedDBAdapter,
	deleteDatabase,
} from "@/services/storage/indexeddb-adapter";
import type { MediaAssetData } from "@/services/storage/types";
import { StorageMigration } from "./base";
import type { ProjectRecord } from "./transformers/types";
import {
	getProjectId,
	transformProjectV1ToV2,
	type TransformV1ToV2Options,
} from "./transformers/v1-to-v2";

export class V1toV2Migration extends StorageMigration {
	from = 1;
	to = 2;

	async transform(project: ProjectRecord): Promise<{
		project: ProjectRecord;
		skipped: boolean;
		reason?: string;
	}> {
		const projectId = getProjectId({ project });
		if (!projectId) {
			return { project, skipped: true, reason: "no project id" };
		}

		const loadMediaAsset = createMediaAssetLoader({ projectId });

		const result = await transformProjectV1ToV2({
			project,
			options: { loadMediaAsset },
		});

		if (!result.skipped) {
			void cleanupLegacyTimelineDBs({
				projectId,
				project: result.project,
			});
		}

		return result;
	}
}

function createMediaAssetLoader({
	projectId,
}: {
	projectId: string;
}): TransformV1ToV2Options["loadMediaAsset"] {
	return async ({ mediaId }: { mediaId: string }) => {
		const mediaMetadataAdapter = new IndexedDBAdapter<MediaAssetData>(
			`video-editor-media-${projectId}`,
			"media-metadata",
			1,
		);

		return mediaMetadataAdapter.get(mediaId);
	};
}

function cleanupLegacyTimelineDBs({
	projectId,
	project,
}: {
	projectId: string;
	project: ProjectRecord;
}): void {
	const scenes = project.scenes;
	if (!Array.isArray(scenes)) {
		return;
	}

	const dbNamesToDelete: string[] = [];

	for (const scene of scenes) {
		if (typeof scene !== "object" || scene === null) {
			continue;
		}

		const sceneId = scene.id;
		if (typeof sceneId === "string") {
			const sceneDbName = `video-editor-timelines-${projectId}-${sceneId}`;
			dbNamesToDelete.push(sceneDbName);
		}
	}

	const projectDbName = `video-editor-timelines-${projectId}`;
	dbNamesToDelete.push(projectDbName);

	// Fire-and-forget: delete in parallel, don't block migration
	void Promise.all(
		dbNamesToDelete.map((dbName) =>
			deleteDatabase({ dbName }).catch(() => {
				// ignore errors, DB might not exist or already deleted
			}),
		),
	);
}