summaryrefslogtreecommitdiff
path: root/apps/web/src/services/storage/service.ts
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2026-03-25 16:47:22 +0100
committerMaze Winther <mazewinther@gmail.com>2026-03-29 15:48:22 +0200
commit8db3bead13441a5882d8689dfc6a9f09c82375d9 (patch)
treee9d0cafa93904ffcbec3188c37961bcc16c6422d /apps/web/src/services/storage/service.ts
parent39ea298a9ccba3104c549c0ca079c231981811f5 (diff)
downloadopencut-8db3bead13441a5882d8689dfc6a9f09c82375d9.tar.gz
feat: masks, properties refactor, shaders, storage migrations, and more
Diffstat (limited to 'apps/web/src/services/storage/service.ts')
-rw-r--r--apps/web/src/services/storage/service.ts52
1 files changed, 45 insertions, 7 deletions
diff --git a/apps/web/src/services/storage/service.ts b/apps/web/src/services/storage/service.ts
index aac152da..3a014894 100644
--- a/apps/web/src/services/storage/service.ts
+++ b/apps/web/src/services/storage/service.ts
@@ -1,20 +1,27 @@
-import type { TProject, TProjectMetadata } from "@/types/project";
+import type { TProject, TProjectMetadata } from "@/lib/project/types";
import { getProjectDurationFromScenes } from "@/lib/scenes";
-import type { MediaAsset } from "@/types/assets";
+import type { MediaAsset } from "@/lib/media/types";
import { IndexedDBAdapter } from "./indexeddb-adapter";
import { OPFSAdapter } from "./opfs-adapter";
+import {
+ type StorageCapacityCheckResult,
+ StorageQuotaExceededError,
+ evaluateStorageCapacity,
+ isStorageQuotaExceededError,
+ readStorageQuotaStatus,
+} from "./quota";
import type {
MediaAssetData,
StorageConfig,
SerializedProject,
SerializedScene,
} from "./types";
-import type { SavedSoundsData, SavedSound, SoundEffect } from "@/types/sounds";
+import type { SavedSoundsData, SavedSound, SoundEffect } from "@/lib/sounds/types";
import {
migrations,
runStorageMigrations,
} from "@/services/storage/migrations";
-import type { Bookmark, TimelineTrack, TScene } from "@/types/timeline";
+import type { Bookmark, TimelineTrack, TScene } from "@/lib/timeline";
function normalizeBookmarks({ raw }: { raw: unknown }): Bookmark[] {
if (!Array.isArray(raw)) return [];
@@ -90,6 +97,22 @@ class StorageService {
return { mediaMetadataAdapter, mediaAssetsAdapter };
}
+ async canStoreFile({
+ size,
+ }: {
+ size: number;
+ }): Promise<StorageCapacityCheckResult> {
+ const quotaStatus = await readStorageQuotaStatus();
+ return evaluateStorageCapacity({
+ requiredBytes: size,
+ quotaStatus,
+ });
+ }
+
+ isQuotaExceededError({ error }: { error: unknown }): boolean {
+ return isStorageQuotaExceededError({ error });
+ }
+
private stripAudioBuffers({
tracks,
}: {
@@ -238,8 +261,6 @@ class StorageService {
const { mediaMetadataAdapter, mediaAssetsAdapter } =
this.getProjectMediaAdapters({ projectId });
- await mediaAssetsAdapter.set(mediaAsset.id, mediaAsset.file);
-
const metadata: MediaAssetData = {
id: mediaAsset.id,
name: mediaAsset.name,
@@ -253,7 +274,24 @@ class StorageService {
ephemeral: mediaAsset.ephemeral,
};
- await mediaMetadataAdapter.set(mediaAsset.id, metadata);
+ try {
+ await mediaAssetsAdapter.set(mediaAsset.id, mediaAsset.file);
+ await mediaMetadataAdapter.set(mediaAsset.id, metadata);
+ } catch (error) {
+ try {
+ await mediaAssetsAdapter.remove(mediaAsset.id);
+ } catch {
+ // Ignore cleanup failures so the original storage error is preserved.
+ }
+
+ if (this.isQuotaExceededError({ error })) {
+ throw new StorageQuotaExceededError({
+ requiredBytes: mediaAsset.file.size,
+ });
+ }
+
+ throw error;
+ }
}
async loadMediaAsset({