diff --git a/src/__tests__/node.test.ts b/src/__tests__/node.test.ts index 851c42580..2d69f7964 100644 --- a/src/__tests__/node.test.ts +++ b/src/__tests__/node.test.ts @@ -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 @@ -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); @@ -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)); @@ -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); @@ -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; @@ -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; @@ -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(); @@ -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(); diff --git a/src/core/Node.ts b/src/core/Node.ts index 8d52a6273..6d0cc6360 100644 --- a/src/core/Node.ts +++ b/src/core/Node.ts @@ -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(); // i-node number. @@ -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.. @@ -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) { @@ -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. @@ -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) { @@ -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; + } +} diff --git a/src/core/Superblock.ts b/src/core/Superblock.ts index 33eb2525d..441aee671 100644 --- a/src/core/Superblock.ts +++ b/src/core/Superblock.ts @@ -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'; @@ -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; } diff --git a/src/core/index.ts b/src/core/index.ts index 938200b60..505745910 100644 --- a/src/core/index.ts +++ b/src/core/index.ts @@ -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'; diff --git a/src/node.ts b/src/node.ts index e6f73c84e..478609fbd 100644 --- a/src/node.ts +++ b/src/node.ts @@ -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';