mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-16 19:57:52 +01:00
fs: get rid of localforage and use generic interface
This commit is contained in:
committed by
Abdullah Atta
parent
ec6fc6839e
commit
c88bc722e1
@@ -17,24 +17,15 @@ You should have received a copy of the GNU General Public License
|
|||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import localforage from "localforage";
|
|
||||||
import FileHandle from "./src/filehandle";
|
import FileHandle from "./src/filehandle";
|
||||||
import { IStreamableFS } from "./src/interfaces";
|
import { IFileStorage, IStreamableFS } from "./src/interfaces";
|
||||||
import { File } from "./src/types";
|
import { File } from "./src/types";
|
||||||
|
|
||||||
export class StreamableFS implements IStreamableFS {
|
export class StreamableFS implements IStreamableFS {
|
||||||
private storage: LocalForage;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param db name of the indexeddb database
|
* @param db name of the indexeddb database
|
||||||
*/
|
*/
|
||||||
constructor(db: string) {
|
constructor(private readonly storage: IFileStorage) {}
|
||||||
this.storage = localforage.createInstance({
|
|
||||||
storeName: "streamable-fs",
|
|
||||||
name: db,
|
|
||||||
driver: [localforage.INDEXEDDB]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async createFile(
|
async createFile(
|
||||||
filename: string,
|
filename: string,
|
||||||
@@ -43,23 +34,24 @@ export class StreamableFS implements IStreamableFS {
|
|||||||
): Promise<FileHandle> {
|
): Promise<FileHandle> {
|
||||||
if (await this.exists(filename)) throw new Error("File already exists.");
|
if (await this.exists(filename)) throw new Error("File already exists.");
|
||||||
|
|
||||||
const file: File = await this.storage.setItem<File>(filename, {
|
const file: File = {
|
||||||
filename,
|
filename,
|
||||||
size,
|
size,
|
||||||
type,
|
type,
|
||||||
chunks: 0
|
chunks: 0
|
||||||
});
|
};
|
||||||
|
await this.storage.setMetadata(filename, file);
|
||||||
return new FileHandle(this.storage, file);
|
return new FileHandle(this.storage, file);
|
||||||
}
|
}
|
||||||
|
|
||||||
async readFile(filename: string): Promise<FileHandle | undefined> {
|
async readFile(filename: string): Promise<FileHandle | undefined> {
|
||||||
const file = await this.storage.getItem<File>(filename);
|
const file = await this.storage.getMetadata(filename);
|
||||||
if (!file) return undefined;
|
if (!file) return undefined;
|
||||||
return new FileHandle(this.storage, file);
|
return new FileHandle(this.storage, file);
|
||||||
}
|
}
|
||||||
|
|
||||||
async exists(filename: string): Promise<boolean> {
|
async exists(filename: string): Promise<boolean> {
|
||||||
const file = await this.storage.getItem<File>(filename);
|
const file = await this.storage.getMetadata(filename);
|
||||||
return !!file;
|
return !!file;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
13
packages/streamable-fs/package-lock.json
generated
13
packages/streamable-fs/package-lock.json
generated
@@ -9,8 +9,7 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "GPL-3.0-or-later",
|
"license": "GPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@notesnook/crypto": "file:../crypto",
|
"@notesnook/crypto": "file:../crypto"
|
||||||
"localforage": "^1.10.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/localforage": "^0.0.34"
|
"@types/localforage": "^0.0.34"
|
||||||
@@ -42,12 +41,14 @@
|
|||||||
"node_modules/immediate": {
|
"node_modules/immediate": {
|
||||||
"version": "3.0.6",
|
"version": "3.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
|
||||||
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="
|
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/lie": {
|
"node_modules/lie": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz",
|
||||||
"integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==",
|
"integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==",
|
||||||
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"immediate": "~3.0.5"
|
"immediate": "~3.0.5"
|
||||||
}
|
}
|
||||||
@@ -56,6 +57,7 @@
|
|||||||
"version": "1.10.0",
|
"version": "1.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz",
|
||||||
"integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==",
|
"integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==",
|
||||||
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lie": "3.1.1"
|
"lie": "3.1.1"
|
||||||
}
|
}
|
||||||
@@ -80,12 +82,14 @@
|
|||||||
"immediate": {
|
"immediate": {
|
||||||
"version": "3.0.6",
|
"version": "3.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
|
||||||
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="
|
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"lie": {
|
"lie": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz",
|
||||||
"integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==",
|
"integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==",
|
||||||
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"immediate": "~3.0.5"
|
"immediate": "~3.0.5"
|
||||||
}
|
}
|
||||||
@@ -94,6 +98,7 @@
|
|||||||
"version": "1.10.0",
|
"version": "1.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz",
|
||||||
"integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==",
|
"integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==",
|
||||||
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"lie": "3.1.1"
|
"lie": "3.1.1"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,7 @@
|
|||||||
"author": "",
|
"author": "",
|
||||||
"license": "GPL-3.0-or-later",
|
"license": "GPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@notesnook/crypto": "file:../crypto",
|
"@notesnook/crypto": "file:../crypto"
|
||||||
"localforage": "^1.10.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/localforage": "^0.0.34"
|
"@types/localforage": "^0.0.34"
|
||||||
|
|||||||
@@ -18,16 +18,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import FileStreamSource from "./filestreamsource";
|
import FileStreamSource from "./filestreamsource";
|
||||||
|
import { IFileStorage } from "./interfaces";
|
||||||
import { File } from "./types";
|
import { File } from "./types";
|
||||||
|
|
||||||
export default class FileHandle {
|
export default class FileHandle {
|
||||||
private storage: LocalForage;
|
constructor(private readonly storage: IFileStorage, readonly file: File) {}
|
||||||
public file: File;
|
|
||||||
|
|
||||||
constructor(storage: LocalForage, file: File) {
|
|
||||||
this.file = file;
|
|
||||||
this.storage = storage;
|
|
||||||
}
|
|
||||||
|
|
||||||
get readable() {
|
get readable() {
|
||||||
return new ReadableStream(new FileStreamSource(this.storage, this.file));
|
return new ReadableStream(new FileStreamSource(this.storage, this.file));
|
||||||
@@ -38,12 +33,15 @@ export default class FileHandle {
|
|||||||
write: async (chunk, controller) => {
|
write: async (chunk, controller) => {
|
||||||
if (controller.signal.aborted) return;
|
if (controller.signal.aborted) return;
|
||||||
|
|
||||||
await this.storage.setItem(this.getChunkKey(this.file.chunks++), chunk);
|
await this.storage.writeChunk(
|
||||||
await this.storage.setItem(this.file.filename, this.file);
|
this.getChunkKey(this.file.chunks++),
|
||||||
|
chunk
|
||||||
|
);
|
||||||
|
await this.storage.setMetadata(this.file.filename, this.file);
|
||||||
},
|
},
|
||||||
abort: async () => {
|
abort: async () => {
|
||||||
for (let i = 0; i < this.file.chunks; ++i) {
|
for (let i = 0; i < this.file.chunks; ++i) {
|
||||||
await this.storage.removeItem(this.getChunkKey(i));
|
await this.storage.deleteChunk(this.getChunkKey(i));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -52,14 +50,14 @@ export default class FileHandle {
|
|||||||
async addAdditionalData<T>(key: string, value: T) {
|
async addAdditionalData<T>(key: string, value: T) {
|
||||||
this.file.additionalData = this.file.additionalData || {};
|
this.file.additionalData = this.file.additionalData || {};
|
||||||
this.file.additionalData[key] = value;
|
this.file.additionalData[key] = value;
|
||||||
await this.storage.setItem(this.file.filename, this.file);
|
await this.storage.setMetadata(this.file.filename, this.file);
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete() {
|
async delete() {
|
||||||
for (let i = 0; i < this.file.chunks; ++i) {
|
for (let i = 0; i < this.file.chunks; ++i) {
|
||||||
await this.storage.removeItem(this.getChunkKey(i));
|
await this.storage.deleteChunk(this.getChunkKey(i));
|
||||||
}
|
}
|
||||||
await this.storage.removeItem(this.file.filename);
|
await this.storage.deleteMetadata(this.file.filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
private getChunkKey(offset: number): string {
|
private getChunkKey(offset: number): string {
|
||||||
@@ -67,10 +65,8 @@ export default class FileHandle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async readChunk(offset: number): Promise<Uint8Array | null> {
|
async readChunk(offset: number): Promise<Uint8Array | null> {
|
||||||
const array = await this.storage.getItem<Uint8Array>(
|
const array = await this.storage.readChunk(this.getChunkKey(offset));
|
||||||
this.getChunkKey(offset)
|
return array || null;
|
||||||
);
|
|
||||||
return array;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async readChunks(from: number, length: number): Promise<Blob> {
|
async readChunks(from: number, length: number): Promise<Blob> {
|
||||||
|
|||||||
@@ -18,16 +18,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { File } from "./types";
|
import { File } from "./types";
|
||||||
|
import { IFileStorage } from "./interfaces";
|
||||||
|
|
||||||
export default class FileStreamSource {
|
export default class FileStreamSource {
|
||||||
private storage: LocalForage;
|
|
||||||
private file: File;
|
|
||||||
private offset = 0;
|
private offset = 0;
|
||||||
|
|
||||||
constructor(storage: LocalForage, file: File) {
|
constructor(
|
||||||
this.storage = storage;
|
private readonly storage: IFileStorage,
|
||||||
this.file = file;
|
private readonly file: File
|
||||||
}
|
) {}
|
||||||
|
|
||||||
start() {}
|
start() {}
|
||||||
|
|
||||||
@@ -42,7 +41,7 @@ export default class FileStreamSource {
|
|||||||
|
|
||||||
private readChunk(offset: number) {
|
private readChunk(offset: number) {
|
||||||
if (offset > this.file.chunks) return;
|
if (offset > this.file.chunks) return;
|
||||||
return this.storage.getItem<Uint8Array>(this.getChunkKey(offset));
|
return this.storage.readChunk(this.getChunkKey(offset));
|
||||||
}
|
}
|
||||||
|
|
||||||
private getChunkKey(offset: number): string {
|
private getChunkKey(offset: number): string {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import FileHandle from "./filehandle";
|
import FileHandle from "./filehandle";
|
||||||
|
import { File } from "./types";
|
||||||
|
|
||||||
export interface IStreamableFS {
|
export interface IStreamableFS {
|
||||||
createFile(filename: string, size: number, type: string): Promise<FileHandle>;
|
createFile(filename: string, size: number, type: string): Promise<FileHandle>;
|
||||||
@@ -26,3 +27,13 @@ export interface IStreamableFS {
|
|||||||
deleteFile(filename: string): Promise<boolean>;
|
deleteFile(filename: string): Promise<boolean>;
|
||||||
clear(): Promise<void>;
|
clear(): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IFileStorage {
|
||||||
|
clear(): Promise<void>;
|
||||||
|
setMetadata(filename: string, metadata: File): Promise<void>;
|
||||||
|
getMetadata(filename: string): Promise<File | undefined>;
|
||||||
|
deleteMetadata(filename: string): Promise<void>;
|
||||||
|
writeChunk(chunkName: string, data: Uint8Array): Promise<void>;
|
||||||
|
deleteChunk(chunkName: string): Promise<void>;
|
||||||
|
readChunk(chunkName: string): Promise<Uint8Array | undefined>;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user