Skip to content

Commit

Permalink
feat: 🎸 implement unlink() method
Browse files Browse the repository at this point in the history
  • Loading branch information
streamich committed Jun 16, 2023
1 parent f9f1619 commit 6bd3e75
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 23 deletions.
74 changes: 51 additions & 23 deletions src/fsa-to-node/FsaNodeFs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,43 @@ import type * as misc from '../node/types/misc';
import type * as opts from '../node/types/options';
import type * as fsa from '../fsa/types';

const notImplemented: (...args: unknown[]) => unknown = () => {
throw new Error('Not implemented');
};
// const notImplemented: (...args: unknown[]) => unknown = () => {
// throw new Error('Not implemented');
// };

/**
* Constructs a Node.js `fs` API from a File System Access API
* [`FileSystemDirectoryHandle` object](https://developer.mozilla.org/en-US/docs/Web/API/FileSystemDirectoryHandle).
*/
export class FsaNodeFs implements FsCallbackApi {
public readonly promises: FsPromisesApi = createPromisesApi(this);
public readonly fs = new Map<number, fsa.IFileSystemFileHandle>();

public constructor(protected readonly root: fsa.IFileSystemDirectoryHandle) {}

/**
* @param path Path from root to the new folder.
* @param create Whether to create the folders if they don't exist.
*/
private async getDir(path: string[], create: boolean, funcName?: string): Promise<fsa.IFileSystemDirectoryHandle> {
let curr: fsa.IFileSystemDirectoryHandle = this.root;
const options: fsa.GetDirectoryHandleOptions = { create };
try {
for (const name of path) curr = await curr.getDirectoryHandle(name, options);
} catch (error) {
if (error && typeof error === 'object' && error.name === 'TypeMismatchError')
throw createError('ENOTDIR', funcName, path.join(FsaToNodeConstants.Separator));
throw error;
}
return curr;
}

// private async getFile(path: string[], name: string, funcName?: string): Promise<fsa.IFileSystemFileHandle> {
// const dir = await this.getDir(path, false, funcName);
// const file = await dir.getFileHandle(name, { create: false });
// return file;
// }

public readonly open: FsCallbackApi['open'] = (
path: misc.PathLike,
flags: misc.TFlags,
Expand Down Expand Up @@ -85,9 +109,30 @@ export class FsaNodeFs implements FsCallbackApi {
throw new Error('Not implemented');
}

unlink(path: misc.PathLike, callback: misc.TCallback<void>): void {
throw new Error('Not implemented');
}
public readonly unlink: FsCallbackApi['unlink'] = (path: misc.PathLike, callback: misc.TCallback<void>): void => {
const filename = pathToFilename(path);
const [folder, name] = pathToLocation(filename);
this.getDir(folder, false, 'unlink')
.then(dir => dir.removeEntry(name))
.then(
() => callback(null),
error => {
if (error && typeof error === 'object') {
switch (error.name) {
case 'NotFoundError': {
callback(createError('ENOENT', 'unlink', filename));
return;
}
case 'InvalidModificationError': {
callback(createError('EISDIR', 'unlink', filename));
return;
}
}
}
callback(error);
},
);
};

symlink(target: misc.PathLike, path: misc.PathLike, callback: misc.TCallback<void>);
symlink(target: misc.PathLike, path: misc.PathLike, type: misc.symlink.Type, callback: misc.TCallback<void>);
Expand Down Expand Up @@ -205,23 +250,6 @@ export class FsaNodeFs implements FsCallbackApi {
throw new Error('Not implemented');
}

/**
* @param path Path from root to the new folder.
* @param create Whether to create the folders if they don't exist.
*/
private async getDir(path: string[], create: boolean, funcName?: string): Promise<fsa.IFileSystemDirectoryHandle> {
let curr: fsa.IFileSystemDirectoryHandle = this.root;
const options: fsa.GetDirectoryHandleOptions = { create };
try {
for (const name of path) curr = await curr.getDirectoryHandle(name, options);
} catch (error) {
if (error && typeof error === 'object' && error.name === 'TypeMismatchError')
throw createError('ENOTDIR', funcName, path.join(FsaToNodeConstants.Separator));
throw error;
}
return curr;
}

public readonly mkdir: FsCallbackApi['mkdir'] = (
path: misc.PathLike,
a: misc.TCallback<void> | misc.TMode | opts.IMkdirOptions,
Expand Down
40 changes: 40 additions & 0 deletions src/fsa-to-node/__tests__/FsaNodeFs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,43 @@ describe('.rm()', () => {
await fs.promises.rm('/lala/lulu', { recursive: true, force: true });
});
});

describe('.unlink()', () => {
test('can remove a file', async () => {
const { fs, mfs } = setup({ folder: { file: 'test' }, 'empty-folder': null });
const res = await fs.promises.unlink('/folder/file');
expect(res).toBe(undefined);
expect(mfs.__vol.toJSON()).toStrictEqual({
'/mountpoint/folder': null,
'/mountpoint/empty-folder': null,
});
});

test('cannot delete a folder', async () => {
const { fs, mfs } = setup({ folder: { file: 'test' }, 'empty-folder': null });
try {
await fs.promises.unlink('/folder');
throw new Error('Expected error');
} catch (error) {
expect(error.code).toBe('EISDIR');
expect(mfs.__vol.toJSON()).toStrictEqual({
'/mountpoint/folder/file': 'test',
'/mountpoint/empty-folder': null,
});
}
});

test('throws when deleting non-existing file', async () => {
const { fs, mfs } = setup({ folder: { file: 'test' }, 'empty-folder': null });
try {
await fs.promises.unlink('/folder/not-a-file');
throw new Error('Expected error');
} catch (error) {
expect(error.code).toBe('ENOENT');
expect(mfs.__vol.toJSON()).toStrictEqual({
'/mountpoint/folder/file': 'test',
'/mountpoint/empty-folder': null,
});
}
});
});

0 comments on commit 6bd3e75

Please sign in to comment.