From 25c91a77dc8f7f9afbce5f1cc360b22ebb29a2e1 Mon Sep 17 00:00:00 2001 From: Bill ZHANG <36790218+Lutra-Fs@users.noreply.github.com> Date: Tue, 16 Jan 2024 17:28:16 +1100 Subject: [PATCH] refactor(IndexedDBRestore,AutoSaveService): use idb (#231) * refactor(IndexedDBRestore,AutoSaveService): use idb instead of native API Signed-off-by: Bill ZHANG <36790218+Lutra-Fs@users.noreply.github.com> * refactor(saveService): fix formatting error Signed-off-by: Bill ZHANG <36790218+Lutra-Fs@users.noreply.github.com> --------- Signed-off-by: Bill ZHANG <36790218+Lutra-Fs@users.noreply.github.com> --- package-lock.json | 60 +++++++---- package.json | 1 + .../RestoreComponents/IndexedDBRestore.tsx | 39 +++---- .../RestoreComponents/RestoreProps.ts | 2 +- src/services/autoSave/autoSaveService.ts | 100 +++++++----------- 5 files changed, 91 insertions(+), 111 deletions(-) diff --git a/package-lock.json b/package-lock.json index dc359e5..18a00e2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@vitejs/plugin-react": "^4.1.0", "ajv": "^8.12.0", "antd": "^5.8.4", + "idb": "^8.0.0", "onnxruntime-web": "^1.16.3", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -3686,6 +3687,22 @@ } } }, + "node_modules/@react-three/drei/node_modules/zustand": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-3.7.2.tgz", + "integrity": "sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==", + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + } + } + }, "node_modules/@react-three/fiber": { "version": "8.15.11", "resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-8.15.11.tgz", @@ -3757,6 +3774,22 @@ "ieee754": "^1.2.1" } }, + "node_modules/@react-three/fiber/node_modules/zustand": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-3.7.2.tgz", + "integrity": "sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==", + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + } + } + }, "node_modules/@rollup/pluginutils": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.4.tgz", @@ -8357,6 +8390,11 @@ "node": ">=0.10.0" } }, + "node_modules/idb": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/idb/-/idb-8.0.0.tgz", + "integrity": "sha512-l//qvlAKGmQO31Qn7xdzagVPPaHTxXx199MhrAFuVBTPqydcPYBWjkrbv4Y0ktB+GmWOiwHl237UUOrLmQxLvw==" + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -14674,9 +14712,9 @@ "dev": true }, "node_modules/vite": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.0.tgz", - "integrity": "sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==", + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.1.tgz", + "integrity": "sha512-AXXFaAJ8yebyqzoNB9fu2pHoo/nWX+xZlaRwoeYUxEqBO+Zj4msE5G+BhGBll9lYEKv9Hfks52PAF2X7qDYXQA==", "dependencies": { "esbuild": "^0.18.10", "postcss": "^8.4.27", @@ -15027,22 +15065,6 @@ "resolved": "https://registry.npmjs.org/zstddec/-/zstddec-0.0.2.tgz", "integrity": "sha512-DCo0oxvcvOTGP/f5FA6tz2Z6wF+FIcEApSTu0zV5sQgn9hoT5lZ9YRAKUraxt9oP7l4e8TnNdi8IZTCX6WCkwA==" }, - "node_modules/zustand": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/zustand/-/zustand-3.7.2.tgz", - "integrity": "sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==", - "engines": { - "node": ">=12.7.0" - }, - "peerDependencies": { - "react": ">=16.8" - }, - "peerDependenciesMeta": { - "react": { - "optional": true - } - } - }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", diff --git a/package.json b/package.json index f3a6c2e..99661da 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@vitejs/plugin-react": "^4.1.0", "ajv": "^8.12.0", "antd": "^5.8.4", + "idb": "^8.0.0", "onnxruntime-web": "^1.16.3", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/src/components/RestoreComponents/IndexedDBRestore.tsx b/src/components/RestoreComponents/IndexedDBRestore.tsx index 14934d2..c3fb403 100644 --- a/src/components/RestoreComponents/IndexedDBRestore.tsx +++ b/src/components/RestoreComponents/IndexedDBRestore.tsx @@ -2,13 +2,14 @@ import styled from 'styled-components'; import { type DeserializeArgs, type IncomingMessage, - RunnerFunc -} from "../../workers/modelWorkerMessage"; + RunnerFunc, +} from '../../workers/modelWorkerMessage'; import type React from 'react'; import { useEffect, useState } from 'react'; import { Divider, List, Typography } from 'antd'; - +import { openDB } from 'idb'; import { type RestoreProps } from './RestoreProps'; +import { type ModelSave } from '../../services/model/modelService'; const Container = styled.div` width: 100%; @@ -16,27 +17,6 @@ const Container = styled.div` z-index: 100; `; -async function getDBEntry(): Promise { - return await new Promise((resolve, reject) => { - const request = window.indexedDB.open('modelAutoSave', 1); - request.onerror = (event) => { - reject(event); - }; - request.onsuccess = (event: Event) => { - const db = (event.target as IDBOpenDBRequest).result; - const transaction = db.transaction('modelSave', 'readonly'); - const objectStore = transaction.objectStore('modelSave'); - const getAllKeysRequest = objectStore.getAllKeys(); - getAllKeysRequest.onsuccess = (event) => { - const keys = (event.target as IDBRequest).result; - if (Array.isArray(keys)) { - resolve(keys); - } - }; - }; - }); -} - export default function IndexedDBRestore( props: RestoreProps, ): React.ReactElement { @@ -45,8 +25,11 @@ export default function IndexedDBRestore( useEffect(() => { async function fetchKeys(): Promise { - const dbKeys = await getDBEntry(); - setKeys(dbKeys); + const db = await openDB('modelAutoSave', 1); + const transaction = db.transaction('modelSave', 'readonly'); + const objectStore = transaction.objectStore('modelSave'); + const allKeys = await objectStore.getAllKeys(); + setKeys(allKeys.map((key) => String(key))); } void fetchKeys(); @@ -70,7 +53,9 @@ export default function IndexedDBRestore( if (value !== undefined) { const message: IncomingMessage = { func: RunnerFunc.DESERIALIZE, - args: { savedState: JSON.parse(value) as unknown as string } satisfies DeserializeArgs, + args: { + savedState: value as ModelSave, + } satisfies DeserializeArgs, }; worker.postMessage(message); } diff --git a/src/components/RestoreComponents/RestoreProps.ts b/src/components/RestoreComponents/RestoreProps.ts index b6791c1..0529596 100644 --- a/src/components/RestoreComponents/RestoreProps.ts +++ b/src/components/RestoreComponents/RestoreProps.ts @@ -1,4 +1,4 @@ -import type React from "react"; +import type React from 'react'; export interface RestoreProps { worker: Worker; diff --git a/src/services/autoSave/autoSaveService.ts b/src/services/autoSave/autoSaveService.ts index 02acb88..e771545 100644 --- a/src/services/autoSave/autoSaveService.ts +++ b/src/services/autoSave/autoSaveService.ts @@ -2,14 +2,16 @@ // to IndexedDB import { type ModelSave } from '../model/modelService'; +import { openDB, type IDBPDatabase } from 'idb'; export default class AutoSaveService { saveInterval: number; intervalObj: ReturnType | undefined; maxAutoSaves: number; getModelSerialized: () => ModelSave; - db!: IDBDatabase; + db!: IDBPDatabase; ready = false; + constructor( getModelSerialized: () => ModelSave, saveInterval = 10000, @@ -19,25 +21,22 @@ export default class AutoSaveService { this.maxAutoSaves = maxAutoSaves; this.getModelSerialized = getModelSerialized; this.intervalObj = undefined; - const dbRequest = indexedDB.open('modelAutoSave', 1); - dbRequest.onupgradeneeded = () => { - const db = dbRequest.result; - this.db = db; - const objectStore = this.db.createObjectStore('modelSave', { - autoIncrement: true, + + openDB('modelAutoSave', 1, { + upgrade(db) { + db.createObjectStore('modelSave', { + keyPath: 'time', + autoIncrement: true, + }); + }, + }) + .then((db) => { + this.db = db; + this.ready = true; + }) + .catch(() => { + throw new Error('Failed to open IndexedDB'); }); - objectStore.transaction.oncomplete = () => { - console.log('Successfully created object store'); - }; - }; - dbRequest.onsuccess = () => { - console.log('Successfully opened IndexedDB'); - this.db = dbRequest.result; - this.ready = true; - }; - dbRequest.onerror = () => { - throw new Error('Failed to open IndexedDB'); - }; } startAutoSave(): void { @@ -47,52 +46,25 @@ export default class AutoSaveService { if (!this.ready) { throw new Error('IndexedDB not ready'); } - this.intervalObj = setInterval(() => { + this.intervalObj = setInterval(async () => { const serialisationData = this.getModelSerialized(); - const modelSaveString = JSON.stringify(serialisationData); - console.log('saving model'); - const transaction = this.db.transaction(['modelSave'], 'readwrite'); - transaction.onerror = () => { - throw new Error('Failed to open transaction'); - }; - const objectStore = transaction.objectStore('modelSave'); - const request = objectStore.add(modelSaveString); - request.onsuccess = () => { - console.log('successfully saved model'); - }; - request.onerror = () => { - throw new Error('Failed to save model'); - }; - const request2 = objectStore.count(); - request2.onsuccess = () => { - const count = request2.result; - console.log('count', count); - if (count > this.maxAutoSaves) { - console.log('deleting old model'); - // use while loop to delete all but the last 5 models - const request3 = objectStore.getAllKeys(); - request3.onsuccess = () => { - const keys = request3.result; - console.log('keys', keys); - const keysToDelete = keys.slice(0, count - this.maxAutoSaves); - console.log('keysToDelete', keysToDelete); - keysToDelete.forEach((key) => { - const request4 = objectStore.delete(key); - request4.onsuccess = () => { - console.log('successfully deleted model'); - }; - request4.onerror = () => { - throw new Error('Failed to delete model'); - }; - }); - } - } - }; - request2.onerror = () => { - throw new Error('Failed to count models'); - }; - transaction.oncomplete = () => { - console.log('Successfully saved model and possibly deleted old models'); + console.log( + '🚀 ~ file: autoSaveService.ts:51 ~ AutoSaveService ~ this.intervalObj=setInterval ~ serialisationData:', + serialisationData, + ); + // Save the model to the database + await this.db.add('modelSave', serialisationData); + // Check if the total count exceeds maxAutoSaves + const count = await this.db.count('modelSave'); + if (count > this.maxAutoSaves) { + // Get the earliest model according to the time (index) + const earliestModel = await this.db.getAllKeys('modelSave', null, 10); + console.log( + '🚀 ~ file: autoSaveService.ts:63 ~ AutoSaveService ~ this.intervalObj=setInterval ~ earliestModel:', + earliestModel, + ); + // Delete the earliest model + await this.db.delete('modelSave', earliestModel[0]); } }, this.saveInterval); }