Skip to content

Commit

Permalink
refactor(IndexedDBRestore,AutoSaveService): use idb (#231)
Browse files Browse the repository at this point in the history
* refactor(IndexedDBRestore,AutoSaveService): use idb instead of native API

Signed-off-by: Bill ZHANG <[email protected]>

* refactor(saveService): fix formatting error

Signed-off-by: Bill ZHANG <[email protected]>

---------

Signed-off-by: Bill ZHANG <[email protected]>
  • Loading branch information
Lutra-Fs authored Jan 16, 2024
1 parent 9ef1080 commit 25c91a7
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 111 deletions.
60 changes: 41 additions & 19 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
39 changes: 12 additions & 27 deletions src/components/RestoreComponents/IndexedDBRestore.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,21 @@ 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%;
height: 100%;
z-index: 100;
`;

async function getDBEntry(): Promise<string[]> {
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 {
Expand All @@ -45,8 +25,11 @@ export default function IndexedDBRestore(

useEffect(() => {
async function fetchKeys(): Promise<void> {
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();
Expand All @@ -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);
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/RestoreComponents/RestoreProps.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type React from "react";
import type React from 'react';

export interface RestoreProps {
worker: Worker;
Expand Down
100 changes: 36 additions & 64 deletions src/services/autoSave/autoSaveService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof setInterval> | undefined;
maxAutoSaves: number;
getModelSerialized: () => ModelSave;
db!: IDBDatabase;
db!: IDBPDatabase;
ready = false;

constructor(
getModelSerialized: () => ModelSave,
saveInterval = 10000,
Expand All @@ -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 {
Expand All @@ -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);
}
Expand Down

0 comments on commit 25c91a7

Please sign in to comment.