Skip to content

Commit

Permalink
Overhauled getHandle
Browse files Browse the repository at this point in the history
Cleaned up error handling
  • Loading branch information
james-pre committed Apr 21, 2024
1 parent 3b92398 commit 6382258
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 101 deletions.
3 changes: 2 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
"@typescript-eslint/ban-types": "warn",
"@typescript-eslint/triple-slash-reference": "warn",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-namespace": "warn"
"@typescript-eslint/no-namespace": "warn",
"@typescript-eslint/consistent-type-imports": "warn"
},
"plugins": ["@typescript-eslint"]
}
17 changes: 12 additions & 5 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,6 @@
"typescript": "5.2.2"
},
"peerDependencies": {
"@zenfs/core": "^0.7.0"
"@zenfs/core": "^0.8.1"
}
}
68 changes: 14 additions & 54 deletions src/IndexedDB.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,13 @@
import type { AsyncStoreOptions, Backend, Ino } from '@zenfs/core';
import { AsyncTransaction, AsyncStore, AsyncStoreFS, ApiError, ErrorCode } from '@zenfs/core';

/**
* Converts a DOMException or a DOMError from an IndexedDB event into a
* standardized ZenFS API error.
* @hidden
*/
function convertError(e: { name: string }, message: string = e.toString()): ApiError {
switch (e.name) {
case 'NotFoundError':
return new ApiError(ErrorCode.ENOENT, message);
case 'QuotaExceededError':
return new ApiError(ErrorCode.ENOSPC, message);
default:
// The rest do not seem to map cleanly to standard error codes.
return new ApiError(ErrorCode.EIO, message);
}
}
import type { AsyncStore, AsyncStoreOptions, AsyncTransaction, Backend, Ino } from '@zenfs/core';
import { AsyncStoreFS } from '@zenfs/core';
import { convertException } from './utils.js';

function wrap<T>(request: IDBRequest<T>): Promise<T> {
return new Promise((resolve, reject) => {
request.onsuccess = () => resolve(request.result);
request.onerror = e => {
e.preventDefault();
reject(new ApiError(ErrorCode.EIO));
reject(convertException(request.error));
};
});
}
Expand All @@ -37,32 +21,20 @@ export class IndexedDBTransaction implements AsyncTransaction {
public store: IDBObjectStore
) {}

public async get(key: Ino): Promise<Uint8Array> {
try {
return await wrap<Uint8Array>(this.store.get(key.toString()));
} catch (e) {
throw convertError(e);
}
public get(key: Ino): Promise<Uint8Array> {
return wrap<Uint8Array>(this.store.get(key.toString()));
}

/**
* @todo return false when add has a key conflict (no error)
*/
public async put(key: Ino, data: Uint8Array, overwrite: boolean): Promise<boolean> {
try {
await wrap(overwrite ? this.store.put(data, key.toString()) : this.store.add(data, key.toString()));
return true;
} catch (e) {
throw convertError(e);
}
await wrap(this.store[overwrite ? 'put' : 'add'](data, key.toString()));
return true;
}

public async remove(key: Ino): Promise<void> {
try {
await wrap(this.store.delete(key.toString()));
} catch (e) {
throw convertError(e);
}
public remove(key: Ino): Promise<void> {
return wrap(this.store.delete(key.toString()));
}

public async commit(): Promise<void> {
Expand All @@ -73,7 +45,7 @@ export class IndexedDBTransaction implements AsyncTransaction {
try {
this.tx.abort();
} catch (e) {
throw convertError(e);
throw convertException(e);
}
}
}
Expand Down Expand Up @@ -105,24 +77,12 @@ export class IndexedDBStore implements AsyncStore {
}

public clear(): Promise<void> {
return new Promise((resolve, reject) => {
try {
const req: IDBRequest = this.db.transaction(this.storeName, 'readwrite').objectStore(this.storeName).clear();
req.onsuccess = () => resolve();
req.onerror = e => {
e.preventDefault();
reject(new ApiError(ErrorCode.EIO));
};
} catch (e) {
reject(convertError(e));
}
});
return wrap(this.db.transaction(this.storeName, 'readwrite').objectStore(this.storeName).clear());
}

public beginTransaction(): IndexedDBTransaction {
const tx = this.db.transaction(this.storeName, 'readwrite'),
objectStore = tx.objectStore(this.storeName);
return new IndexedDBTransaction(tx, objectStore);
const tx = this.db.transaction(this.storeName, 'readwrite');
return new IndexedDBTransaction(tx, tx.objectStore(this.storeName));
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/Storage.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Backend, Ino } from '@zenfs/core';
import { SyncStore, SimpleSyncStore, SimpleSyncTransaction, SyncStoreFS, ApiError, ErrorCode, encode, decode } from '@zenfs/core';
import type { Backend, Ino, SimpleSyncStore, SyncStore } from '@zenfs/core';
import { ApiError, ErrorCode, SimpleSyncTransaction, SyncStoreFS, decode, encode } from '@zenfs/core';

/**
* A synchronous key-value store backed by Storage.
Expand Down
66 changes: 28 additions & 38 deletions src/access.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Backend, FileSystemMetadata } from '@zenfs/core';
import { ApiError, Async, ErrorCode, FileSystem, FileType, InMemory, PreloadFile, Stats } from '@zenfs/core';
import { basename, dirname, join } from '@zenfs/core/emulation/path.js';
import { convertException } from './utils.js';

declare global {
interface FileSystemDirectoryHandle {
Expand All @@ -15,14 +16,6 @@ export interface WebAccessOptions {
handle: FileSystemDirectoryHandle;
}

const handleError = (path = '', syscall: string, error: Error) => {
if (error.name === 'NotFoundError') {
throw ApiError.With('ENOENT', path, syscall);
}

throw error as ApiError;
};

export class WebAccessFS extends Async(FileSystem) {
private _handles: Map<string, FileSystemHandle> = new Map();

Expand Down Expand Up @@ -86,8 +79,8 @@ export class WebAccessFS extends Async(FileSystem) {

writable.close();
await this.unlink(oldPath);
} catch (err) {
handleError(oldPath, 'rename', err);
} catch (ex) {
throw convertException(ex, oldPath, 'rename');
}
}

Expand Down Expand Up @@ -137,8 +130,8 @@ export class WebAccessFS extends Async(FileSystem) {
if (handle instanceof FileSystemDirectoryHandle) {
try {
await handle.removeEntry(basename(path), { recursive: true });
} catch (e) {
handleError(path, 'unlink', e);
} catch (ex) {
throw convertException(ex, path, 'unlink');
}
}
}
Expand Down Expand Up @@ -180,40 +173,37 @@ export class WebAccessFS extends Async(FileSystem) {
return this._handles.get(path);
}

let walkedPath = '/';
const [, ...pathParts] = path.split('/');
const getHandleParts = async ([pathPart, ...remainingPathParts]: string[]) => {
const walkingPath = join(walkedPath, pathPart);
const continueWalk = (handle: FileSystemHandle) => {
walkedPath = walkingPath;
this._handles.set(walkedPath, handle);
let walked = '/';

if (remainingPathParts.length === 0) {
return this._handles.get(path);
}

getHandleParts(remainingPathParts);
};
const handle = this._handles.get(walkedPath) as FileSystemDirectoryHandle;
for (const part of path.split('/').slice(1)) {
const handle = this._handles.get(walked);
if (!(handle instanceof FileSystemDirectoryHandle)) {
throw ApiError.With('ENOTDIR', walked, 'getHandle');
}
walked = join(walked, part);

try {
return continueWalk(await handle.getDirectoryHandle(pathPart));
} catch (error) {
if (error.name === 'TypeMismatchError') {
const dirHandle = await handle.getDirectoryHandle(part);
this._handles.set(walked, dirHandle);
} catch (ex) {
if (ex.name == 'TypeMismatchError') {
try {
return continueWalk(await handle.getFileHandle(pathPart));
} catch (err) {
handleError(walkingPath, 'getHandle', err);
const fileHandle = await handle.getFileHandle(part);
this._handles.set(walked, fileHandle);
} catch (ex) {
convertException(ex, walked, 'getHandle');
}
} else if (error.message === 'Name is not allowed.') {
throw new ApiError(ErrorCode.ENOENT, error.message, walkingPath);
} else {
handleError(walkingPath, 'getHandle', error);
}

if (ex.name === 'TypeError') {
throw new ApiError(ErrorCode.ENOENT, ex.message, walked, 'getHandle');
}

convertException(ex, walked, 'getHandle');
}
};
}

return await getHandleParts(pathParts);
return this._handles.get(path);
}
}

Expand Down
70 changes: 70 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { ApiError, ErrorCode } from '@zenfs/core';

/**
* Converts a DOMException into an ErrorCode
* @see https://developer.mozilla.org/Web/API/DOMException
*/
function errnoForDOMException(ex: DOMException): keyof typeof ErrorCode {
switch (ex.name) {
case 'IndexSizeError':
case 'HierarchyRequestError':
case 'InvalidCharacterError':
case 'InvalidStateError':
case 'SyntaxError':
case 'NamespaceError':
case 'TypeMismatchError':
case 'ConstraintError':
case 'VersionError':
case 'URLMismatchError':
case 'InvalidNodeTypeError':
return 'EINVAL';
case 'WrongDocumentError':
return 'EXDEV';
case 'NoModificationAllowedError':
case 'InvalidModificationError':
case 'InvalidAccessError':
case 'SecurityError':
case 'NotAllowedError':
return 'EACCES';
case 'NotFoundError':
return 'ENOENT';
case 'NotSupportedError':
return 'ENOTSUP';
case 'InUseAttributeError':
return 'EBUSY';
case 'NetworkError':
return 'ENETDOWN';
case 'AbortError':
return 'EINTR';
case 'QuotaExceededError':
return 'ENOSPC';
case 'TimeoutError':
return 'ETIMEDOUT';
case 'ReadOnlyError':
return 'EROFS';
case 'DataCloneError':
case 'EncodingError':
case 'NotReadableError':
case 'DataError':
case 'TransactionInactiveError':
case 'OperationError':
case 'UnknownError':
default:
return 'EIO';
}
}

/**
* Handles converting errors, then rethrowing them
*/
export function convertException(ex: Error | ApiError | DOMException, path?: string, syscall?: string): ApiError {
if (ex instanceof ApiError) {
return ex;
}

const code = ex instanceof DOMException ? ErrorCode[errnoForDOMException(ex)] : ErrorCode.EIO;
const error = new ApiError(code, ex.message, path, syscall);
error.stack = ex.stack;
error.cause = ex.cause;
return error;
}

0 comments on commit 6382258

Please sign in to comment.