Skip to content

Commit

Permalink
feat: 🎸 add createSwapFile() method
Browse files Browse the repository at this point in the history
  • Loading branch information
streamich committed Jun 15, 2023
1 parent b006b2d commit b07ce79
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 2 deletions.
4 changes: 3 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,14 @@ export const fs: IFs = createFsFromVolume(vol);
* @returns A `memfs` file system instance, which is a drop-in replacement for
* the `fs` module.
*/
export const memfs = (json: DirectoryJSON = {}, cwd: string = '/') => {
export const memfs = (json: DirectoryJSON = {}, cwd: string = '/'): IFs => {
const volume = Volume.fromJSON(json, cwd);
const fs = createFsFromVolume(volume);
return fs;
};

export type IFsWithVolume = IFs & { __vol: _Volume };

declare let module;
module.exports = { ...module.exports, ...fs };
module.exports.semantic = true;
37 changes: 37 additions & 0 deletions src/node-to-fsa/NodeFileSystemWritableFileStream.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,43 @@
import type { IFileHandle } from '../promises';
import type { NodeFsaFs } from './types';

/**
* When Chrome writes to the file, it creates a copy of the file with extension
* `.crswap` and then replaces the original file with the copy only when the
* `close()` method is called. If the `abort()` method is called, the `.crswap`
* file is deleted.
*
* If a file name with with extension `.crswap` is already taken, it
* creates a new swap file with extension `.1.crswap` and so on.
*/
export const createSwapFile = async (fs: NodeFsaFs, path: string, keepExistingData: boolean): Promise<IFileHandle> => {
let handle: undefined | IFileHandle;
let swapPath: string = path + '.crswap';
try {
handle = await fs.promises.open(swapPath, 'ax');
} catch (error) {
if (!error || typeof error !== 'object' || error.code !== 'EEXIST')
throw error;
}
if (!handle) {
for (let i = 1; i < 1000; i++) {
try {
swapPath = `${path}.${i}.crswap`;
handle = await fs.promises.open(swapPath, 'ax');
break;
} catch (error) {
if (!error || typeof error !== 'object' || error.code !== 'EEXIST')
throw error;
}
}
}
if (!handle) throw new Error(`Could not create a swap file for "${path}".`);
if (keepExistingData)
await fs.promises.copyFile(path, swapPath, fs.constants.COPYFILE_FICLONE);
return handle;
};


interface Ref {
handle: IFileHandle | undefined;
offset: number;
Expand Down
95 changes: 95 additions & 0 deletions src/node-to-fsa/__tests__/NodeFileSystemWritableFileStream.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import {IFsWithVolume, memfs} from '../..';
import {FileHandle} from '../../promises';
import {createSwapFile} from '../NodeFileSystemWritableFileStream';

describe('createSwapFile()', () => {
test('can create a swap file', async () => {
const fs = memfs({
'/file.txt': 'Hello, world!',
}, '/') as IFsWithVolume;
const handle = await createSwapFile(fs, '/file.txt', false);
expect(handle).toBeInstanceOf(FileHandle);
expect(fs.__vol.toJSON()).toEqual({
'/file.txt': 'Hello, world!',
'/file.txt.crswap': '',
});
});

test('can create a swap file at subfolder', async () => {
const fs = memfs({
'/foo/file.txt': 'Hello, world!',
}, '/') as IFsWithVolume;
const handle = await createSwapFile(fs, '/foo/file.txt', false);
expect(handle).toBeInstanceOf(FileHandle);
expect(fs.__vol.toJSON()).toEqual({
'/foo/file.txt': 'Hello, world!',
'/foo/file.txt.crswap': '',
});
});

test('can create a swap file when the default swap file name is in use', async () => {
const fs = memfs({
'/foo/file.txt': 'Hello, world!',
'/foo/file.txt.crswap': 'lala',
}, '/') as IFsWithVolume;
const handle = await createSwapFile(fs, '/foo/file.txt', false);
expect(handle).toBeInstanceOf(FileHandle);
expect(fs.__vol.toJSON()).toEqual({
'/foo/file.txt': 'Hello, world!',
'/foo/file.txt.crswap': 'lala',
'/foo/file.txt.1.crswap': '',
});
});

test('can create a swap file when the first two names are already taken', async () => {
const fs = memfs({
'/foo/file.txt': 'Hello, world!',
'/foo/file.txt.crswap': 'lala',
'/foo/file.txt.1.crswap': 'blah',
}, '/') as IFsWithVolume;
const handle = await createSwapFile(fs, '/foo/file.txt', false);
expect(handle).toBeInstanceOf(FileHandle);
expect(fs.__vol.toJSON()).toEqual({
'/foo/file.txt': 'Hello, world!',
'/foo/file.txt.crswap': 'lala',
'/foo/file.txt.1.crswap': 'blah',
'/foo/file.txt.2.crswap': '',
});
});

test('can create a swap file when the first three names are already taken', async () => {
const fs = memfs({
'/foo/file.txt': 'Hello, world!',
'/foo/file.txt.crswap': 'lala',
'/foo/file.txt.1.crswap': 'blah',
'/foo/file.txt.2.crswap': 'brawo',
}, '/') as IFsWithVolume;
const handle = await createSwapFile(fs, '/foo/file.txt', false);
expect(handle).toBeInstanceOf(FileHandle);
expect(fs.__vol.toJSON()).toEqual({
'/foo/file.txt': 'Hello, world!',
'/foo/file.txt.crswap': 'lala',
'/foo/file.txt.1.crswap': 'blah',
'/foo/file.txt.2.crswap': 'brawo',
'/foo/file.txt.3.crswap': '',
});
});

test('can copy existing data into the swap file', async () => {
const fs = memfs({
'/foo/file.txt': 'Hello, world!',
'/foo/file.txt.crswap': 'lala',
'/foo/file.txt.1.crswap': 'blah',
'/foo/file.txt.2.crswap': 'brawo',
}, '/') as IFsWithVolume;
const handle = await createSwapFile(fs, '/foo/file.txt', true);
expect(handle).toBeInstanceOf(FileHandle);
expect(fs.__vol.toJSON()).toEqual({
'/foo/file.txt': 'Hello, world!',
'/foo/file.txt.crswap': 'lala',
'/foo/file.txt.1.crswap': 'blah',
'/foo/file.txt.2.crswap': 'brawo',
'/foo/file.txt.3.crswap': 'Hello, world!',
});
});
});
2 changes: 1 addition & 1 deletion src/node-to-fsa/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { IFs } from '..';
*/
export type NodeFsaFs = Pick<
IFs,
'promises' | 'openSync' | 'fsyncSync' | 'statSync' | 'closeSync' | 'readSync' | 'truncateSync' | 'writeSync'
'promises' | 'constants' | 'openSync' | 'fsyncSync' | 'statSync' | 'closeSync' | 'readSync' | 'truncateSync' | 'writeSync'
>;

export interface NodeFsaContext {
Expand Down

0 comments on commit b07ce79

Please sign in to comment.