Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 16 additions & 16 deletions src/__tests__/node.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Node, Link } from '../node';
import { Node, FileNode, Link } from '../node';
import { constants } from '../constants';

describe('node.ts', () => {
describe('Node', () => {
const node = new Node(1);
describe('FileNode', () => {
const node = new FileNode(1);
it('properly sets mode with permission respected', () => {
const node = new Node(1, constants.S_IFREG | 0o755);
const node = new FileNode(1, constants.S_IFREG | 0o755);
expect(node.perm).toBe(0o755);
expect(node.mode).toBe(constants.S_IFREG | 0o755);
expect(node.isFile()).toBe(true); // Make sure we still know it's a file
Expand All @@ -18,33 +18,33 @@ describe('node.ts', () => {
});
describe('.write(buf, off, len, pos)', () => {
it('Simple write into empty node', () => {
const node = new Node(1);
const node = new FileNode(1);
node.write(Buffer.from([1, 2, 3]));
expect(node.getBuffer().equals(Buffer.from([1, 2, 3]))).toBe(true);
});
it('Append to the end', () => {
const node = new Node(1);
const node = new FileNode(1);
node.write(Buffer.from([1, 2]));
node.write(Buffer.from([3, 4]), 0, 2, 2);
const result = Buffer.from([1, 2, 3, 4]);
expect(node.getBuffer().equals(result)).toBe(true);
});
it('Overwrite part of the buffer', () => {
const node = new Node(1);
const node = new FileNode(1);
node.write(Buffer.from([1, 2, 3]));
node.write(Buffer.from([4, 5, 6]), 1, 2, 1);
const result = Buffer.from([1, 5, 6]);
expect(node.getBuffer().equals(result)).toBe(true);
});
it('Overwrite part of the buffer and extend', () => {
const node = new Node(1);
const node = new FileNode(1);
node.write(Buffer.from([1, 2, 3]));
node.write(Buffer.from([4, 5, 6, 7]), 0, 4, 2);
const result = Buffer.from([1, 2, 4, 5, 6, 7]);
expect(node.getBuffer().equals(result)).toBe(true);
});
it('Write outside the space of the buffer', () => {
const node = new Node(1);
const node = new FileNode(1);
node.write(Buffer.from([1, 2, 3]));
node.write(Buffer.from([7, 8, 9]), 0, 3, 6);
node.write(Buffer.from([4, 5, 6]), 0, 3, 3);
Expand All @@ -54,14 +54,14 @@ describe('node.ts', () => {
});
describe('.read(buf, off, len, pos)', () => {
it('Simple one byte read', () => {
const node = new Node(1);
const node = new FileNode(1);
node.write(Buffer.from([1, 2, 3]));
const buf = Buffer.allocUnsafe(1);
node.read(buf, 0, 1, 1);
expect(buf.equals(Buffer.from([2]))).toBe(true);
});
it('updates the atime but not ctime', () => {
const node = new Node(1);
const node = new FileNode(1);
const oldAtime = node.atime;
const oldCtime = node.ctime;
node.read(Buffer.alloc(0));
Expand All @@ -72,7 +72,7 @@ describe('node.ts', () => {
});
});
describe('.chmod(perm)', () => {
const node = new Node(1, constants.S_IFREG | 0o666);
const node = new FileNode(1, constants.S_IFREG | 0o666);
expect(node.perm).toBe(0o666);
expect(node.isFile()).toBe(true);
node.chmod(0o600);
Expand All @@ -81,7 +81,7 @@ describe('node.ts', () => {
});
describe.each(['uid', 'gid', 'mtime', 'perm', 'nlink'])('when %s changes', field => {
it('updates the property and the ctime', () => {
const node = new Node(1);
const node = new FileNode(1);
const oldCtime = node.ctime;
node[field] = 1;
const newCtime = node.ctime;
Expand All @@ -91,7 +91,7 @@ describe('node.ts', () => {
});
describe('when atime changes', () => {
it('updates the property but NOT the ctime', () => {
const node = new Node(1);
const node = new FileNode(1);
const oldCtime = node.ctime;
node.atime = new Date(1);
const newCtime = node.ctime;
Expand All @@ -101,7 +101,7 @@ describe('node.ts', () => {
});
describe('.getString(encoding?)', () => {
it('updates the atime but not ctime', () => {
const node = new Node(1);
const node = new FileNode(1);
const oldAtime = node.atime;
const oldCtime = node.ctime;
node.getString();
Expand All @@ -113,7 +113,7 @@ describe('node.ts', () => {
});
describe('.getBuffer()', () => {
it('updates the atime but not ctime', () => {
const node = new Node(1);
const node = new FileNode(1);
const oldAtime = node.atime;
const oldCtime = node.ctime;
node.getBuffer();
Expand Down
166 changes: 111 additions & 55 deletions src/core/Node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ const getuid = (): number => process.getuid?.() ?? 0;
const getgid = (): number => process.getgid?.() ?? 0;

/**
* Node in a file system (like i-node, v-node).
* Base node class in a file system (like i-node, v-node).
*/
export class Node {
export abstract class Node {
public readonly changes = new FanOut<NodeEvent>();

// i-node number.
Expand All @@ -30,8 +30,6 @@ export class Node {
private _mtime = new Date();
private _ctime = new Date();

// data: string = '';
buf: Buffer;
rdev: number = 0;

mode: number; // S_IFDIR, S_IFREG, etc..
Expand Down Expand Up @@ -109,29 +107,23 @@ export class Node {
}

getString(encoding = 'utf8'): string {
this.atime = new Date();
return this.getBuffer().toString(encoding);
throw new Error('Method not implemented for this node type');
}

setString(str: string) {
// this.setBuffer(bufferFrom(str, 'utf8'));
this.buf = bufferFrom(str, 'utf8');
this.touch();
throw new Error('Method not implemented for this node type');
}

getBuffer(): Buffer {
this.atime = new Date();
if (!this.buf) this.buf = bufferAllocUnsafe(0);
return bufferFrom(this.buf); // Return a copy.
throw new Error('Method not implemented for this node type');
}

setBuffer(buf: Buffer) {
this.buf = bufferFrom(buf); // Creates a copy of data.
this.touch();
throw new Error('Method not implemented for this node type');
}

getSize(): number {
return this.buf ? this.buf.length : 0;
return 0;
}

setModeProperty(property: number) {
Expand Down Expand Up @@ -161,19 +153,7 @@ export class Node {
}

write(buf: Buffer, off: number = 0, len: number = buf.length, pos: number = 0): number {
if (!this.buf) this.buf = bufferAllocUnsafe(0);

if (pos + len > this.buf.length) {
const newBuf = bufferAllocUnsafe(pos + len);
this.buf.copy(newBuf, 0, 0, this.buf.length);
this.buf = newBuf;
}

buf.copy(this.buf, pos, off, off + len);

this.touch();

return len;
throw new Error('Method not implemented for this node type');
}

// Returns the number of bytes read.
Expand All @@ -183,36 +163,11 @@ export class Node {
len: number = buf.byteLength,
pos: number = 0,
): number {
this.atime = new Date();
if (!this.buf) this.buf = bufferAllocUnsafe(0);
if (pos >= this.buf.length) return 0;
let actualLen = len;
if (actualLen > buf.byteLength) {
actualLen = buf.byteLength;
}
if (actualLen + pos > this.buf.length) {
actualLen = this.buf.length - pos;
}
const buf2 = buf instanceof Buffer ? buf : Buffer.from(buf.buffer);
this.buf.copy(buf2, off, pos, pos + actualLen);
return actualLen;
throw new Error('Method not implemented for this node type');
}

truncate(len: number = 0) {
if (!len) this.buf = bufferAllocUnsafe(0);
else {
if (!this.buf) this.buf = bufferAllocUnsafe(0);
if (len <= this.buf.length) {
this.buf = this.buf.slice(0, len);
} else {
const buf = bufferAllocUnsafe(len);
this.buf.copy(buf);
buf.fill(0, this.buf.length);
this.buf = buf;
}
}

this.touch();
throw new Error('Method not implemented for this node type');
}

chmod(perm: number) {
Expand Down Expand Up @@ -307,7 +262,108 @@ export class Node {
mode: this.mode,
nlink: this.nlink,
symlink: this.symlink,
};
}
}

/**
* Node representing a file in a file system.
*/
export class FileNode extends Node {
buf: Buffer;

getString(encoding = 'utf8'): string {
this.atime = new Date();
return this.getBuffer().toString(encoding);
}

setString(str: string) {
this.buf = bufferFrom(str, 'utf8');
this.touch();
}

getBuffer(): Buffer {
this.atime = new Date();
if (!this.buf) this.buf = bufferAllocUnsafe(0);
return bufferFrom(this.buf); // Return a copy.
}

setBuffer(buf: Buffer) {
this.buf = bufferFrom(buf); // Creates a copy of data.
this.touch();
}

getSize(): number {
return this.buf ? this.buf.length : 0;
}

write(buf: Buffer, off: number = 0, len: number = buf.length, pos: number = 0): number {
if (!this.buf) this.buf = bufferAllocUnsafe(0);

if (pos + len > this.buf.length) {
const newBuf = bufferAllocUnsafe(pos + len);
this.buf.copy(newBuf, 0, 0, this.buf.length);
this.buf = newBuf;
}

buf.copy(this.buf, pos, off, off + len);

this.touch();

return len;
}

read(
buf: Buffer | ArrayBufferView | DataView,
off: number = 0,
len: number = buf.byteLength,
pos: number = 0,
): number {
this.atime = new Date();
if (!this.buf) this.buf = bufferAllocUnsafe(0);
if (pos >= this.buf.length) return 0;
let actualLen = len;
if (actualLen > buf.byteLength) {
actualLen = buf.byteLength;
}
if (actualLen + pos > this.buf.length) {
actualLen = this.buf.length - pos;
}
const buf2 = buf instanceof Buffer ? buf : Buffer.from(buf.buffer);
this.buf.copy(buf2, off, pos, pos + actualLen);
return actualLen;
}

truncate(len: number = 0) {
if (!len) this.buf = bufferAllocUnsafe(0);
else {
if (!this.buf) this.buf = bufferAllocUnsafe(0);
if (len <= this.buf.length) {
this.buf = this.buf.slice(0, len);
} else {
const buf = bufferAllocUnsafe(len);
this.buf.copy(buf);
buf.fill(0, this.buf.length);
this.buf = buf;
}
}

this.touch();
}

toJSON() {
return {
...super.toJSON(),
data: this.getString(),
};
}
}

/**
* Node representing a directory in a file system.
*/
export class DirNode extends Node {
getSize(): number {
return 0;
}
}
5 changes: 3 additions & 2 deletions src/core/Superblock.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as NodePath from 'path';
import { Node } from './Node';
import { Node, FileNode, DirNode } from './Node';
import { Link } from './Link';
import { File } from './File';
import { Buffer } from '../internal/buffer';
Expand Down Expand Up @@ -150,7 +150,8 @@ export class Superblock {
}

createNode(mode: number): Node {
const node = new Node(this.newInoNumber(), mode);
const isDirectory = (mode & constants.S_IFMT) === constants.S_IFDIR;
const node = isDirectory ? new DirNode(this.newInoNumber(), mode) : new FileNode(this.newInoNumber(), mode);
this.inodes[node.ino] = node;
return node;
}
Expand Down
2 changes: 1 addition & 1 deletion src/core/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export * from './types';
export * from './json';
export { Node } from './Node';
export { Node, FileNode, DirNode } from './Node';
export { Link } from './Link';
export { File } from './File';
export { Superblock } from './Superblock';
2 changes: 1 addition & 1 deletion src/node.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export { Node } from './core/Node';
export { Node, FileNode, DirNode } from './core/Node';
export { Link } from './core/Link';
export { File } from './core/File';
Loading