Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: ensure metadata is updated correctly so that watchFile is correctly triggered #891

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
46 changes: 45 additions & 1 deletion src/__tests__/node.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Node } from '../node';
import { Node, Link } from '../node';
import { constants } from '../constants';

describe('node.ts', () => {
Expand Down Expand Up @@ -60,6 +60,16 @@ describe('node.ts', () => {
node.read(buf, 0, 1, 1);
expect(buf.equals(Buffer.from([2]))).toBe(true);
});
it('updates the atime and ctime', () => {
const node = new Node(1);
const oldAtime = node.atime;
const oldCtime = node.ctime;
node.read(Buffer.alloc(0));
const newAtime = node.atime;
const newCtime = node.ctime;
expect(newAtime).not.toBe(oldAtime);
expect(newCtime).not.toBe(oldCtime);
});
});
describe('.chmod(perm)', () => {
const node = new Node(1);
Expand All @@ -69,5 +79,39 @@ describe('node.ts', () => {
expect(node.perm).toBe(0o600);
expect(node.isFile()).toBe(true);
});
describe.each(['uid', 'gid', 'atime', 'mtime', 'perm', 'nlink'])('when %s changes', field => {
it('updates the property and the ctime', () => {
const node = new Node(1);
const oldCtime = node.ctime;
node[field] = 1;
const newCtime = node.ctime;
expect(newCtime).not.toBe(oldCtime);
expect(node[field]).toBe(1);
});
});
describe('.getString(encoding?)', () => {
it('updates the atime and ctime', () => {
const node = new Node(1);
const oldAtime = node.atime;
const oldCtime = node.ctime;
node.getString();
const newAtime = node.atime;
const newCtime = node.ctime;
expect(newAtime).not.toBe(oldAtime);
expect(newCtime).not.toBe(oldCtime);
});
});
describe('.getBuffer()', () => {
it('updates the atime and ctime', () => {
const node = new Node(1);
const oldAtime = node.atime;
const oldCtime = node.ctime;
node.getBuffer();
const newAtime = node.atime;
const newCtime = node.ctime;
expect(newAtime).not.toBe(oldAtime);
expect(newCtime).not.toBe(oldCtime);
});
});
});
});
9 changes: 9 additions & 0 deletions src/__tests__/volume.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -332,10 +332,13 @@ describe('volume', () => {
describe('.openSync(path, flags[, mode])', () => {
const vol = new Volume();
it('Create new file at root (/test.txt)', () => {
const oldMtime = vol.root.getNode().mtime;
const fd = vol.openSync('/test.txt', 'w');
const newMtime = vol.root.getNode().mtime;
expect(vol.root.getChild('test.txt')).toBeInstanceOf(Link);
expect(typeof fd).toBe('number');
expect(fd).toBeGreaterThan(0);
expect(oldMtime).not.toBe(newMtime);
});
it('Error on file not found', () => {
try {
Expand Down Expand Up @@ -989,10 +992,16 @@ describe('volume', () => {
describe('.mkdirSync(path[, options])', () => {
it('Create dir at root', () => {
const vol = new Volume();
const oldMtime = vol.root.getNode().mtime;
const oldNlink = vol.root.getNode().nlink;
vol.mkdirSync('/test');
const newMtime = vol.root.getNode().mtime;
const newNlink = vol.root.getNode().nlink;
const child = tryGetChild(vol.root, 'test');
expect(child).toBeInstanceOf(Link);
expect(child.getNode().isDirectory()).toBe(true);
expect(oldMtime).not.toBe(newMtime);
expect(newNlink).toBe(oldNlink + 1);
});
it('Create 2 levels deep folders', () => {
const vol = new Volume();
Expand Down
108 changes: 94 additions & 14 deletions src/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,34 +19,97 @@ export class Node extends EventEmitter {
ino: number;

// User ID and group ID.
uid: number = getuid();
gid: number = getgid();
private _uid: number = getuid();
private _gid: number = getgid();

atime = new Date();
mtime = new Date();
ctime = new Date();
private _atime = new Date();
private _mtime = new Date();
private _ctime = new Date();

// data: string = '';
buf: Buffer;

perm = 0o666; // Permissions `chmod`, `fchmod`
private _perm = 0o666; // Permissions `chmod`, `fchmod`

mode = S_IFREG; // S_IFDIR, S_IFREG, etc.. (file by default?)

// Number of hard links pointing at this Node.
nlink = 1;
private _nlink = 1;

// Steps to another node, if this node is a symlink.
symlink: string[];

constructor(ino: number, perm: number = 0o666) {
super();
this.perm = perm;
this._perm = perm;
this.mode |= perm;
this.ino = ino;
}

public set ctime(ctime: Date) {
this._ctime = ctime;
}

public get ctime(): Date {
return this._ctime;
}

public set uid(uid: number) {
this._uid = uid;
this.ctime = new Date();
}

public get uid(): number {
return this._uid;
}

public set gid(gid: number) {
this._gid = gid;
this.ctime = new Date();
}

public get gid(): number {
return this._gid;
}

public set atime(atime: Date) {
this._atime = atime;
this.ctime = new Date();
}

public get atime(): Date {
return this._atime;
}

public set mtime(mtime: Date) {
this._mtime = mtime;
this.ctime = new Date();
}

public get mtime(): Date {
return this._mtime;
}

public set perm(perm: number) {
this._perm = perm;
this.ctime = new Date();
}

public get perm(): number {
return this._perm;
}

public set nlink(nlink: number) {
this._nlink = nlink;
this.ctime = new Date();
}

public get nlink(): number {
return this._nlink;
}

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

Expand All @@ -57,6 +120,7 @@ export class Node extends EventEmitter {
}

getBuffer(): Buffer {
this.atime = new Date();
if (!this.buf) this.setBuffer(bufferAllocUnsafe(0));
return bufferFrom(this.buf); // Return a copy.
}
Expand Down Expand Up @@ -122,6 +186,7 @@ export class Node extends EventEmitter {

// Returns the number of bytes read.
read(buf: Buffer | Uint8Array, off: number = 0, len: number = buf.byteLength, pos: number = 0): number {
this.atime = new Date();
if (!this.buf) this.buf = bufferAllocUnsafe(0);

let actualLen = len;
Expand Down Expand Up @@ -262,8 +327,11 @@ export class Link extends EventEmitter {
// Recursively sync children steps, e.g. in case of dir rename
set steps(val) {
this._steps = val;
for (const child of Object.values(this.children)) {
child?.syncSteps();
for (const [child, link] of Object.entries(this.children)) {
if (child === '.' || child === '..') {
continue;
}
link?.syncSteps();
}
}

Expand All @@ -289,10 +357,8 @@ export class Link extends EventEmitter {
link.setNode(node);

if (node.isDirectory()) {
// link.setChild('.', link);
// link.getNode().nlink++;
// link.setChild('..', this);
// this.getNode().nlink++;
link.children['.'] = link;
link.getNode().nlink++;
}

this.setChild(name, link);
Expand All @@ -305,19 +371,33 @@ export class Link extends EventEmitter {
link.parent = this;
this.length++;

const node = link.getNode();
if (node.isDirectory()) {
link.children['..'] = this;
this.getNode().nlink++;
}

this.getNode().mtime = new Date();
this.emit('child:add', link, this);

return link;
}

deleteChild(link: Link) {
const node = link.getNode();
if (node.isDirectory()) {
delete link.children['..'];
this.getNode().nlink--;
}
delete this.children[link.getName()];
this.length--;

this.getNode().mtime = new Date();
this.emit('child:delete', link, this);
}

getChild(name: string): Link | undefined {
this.getNode().mtime = new Date();
if (Object.hasOwnProperty.call(this.children, name)) {
return this.children[name];
}
Expand Down
24 changes: 15 additions & 9 deletions src/volume.ts
Original file line number Diff line number Diff line change
Expand Up @@ -666,11 +666,11 @@ export class Volume {
}
};

// root.setChild('.', root);
// root.getNode().nlink++;
root.setChild('.', root);
root.getNode().nlink++;

// root.setChild('..', root);
// root.getNode().nlink++;
root.setChild('..', root);
root.getNode().nlink++;

this.root = root;
}
Expand Down Expand Up @@ -879,6 +879,9 @@ export class Volume {
}

for (const name in children) {
if (name === '.' || name === '..') {
continue;
}
isEmpty = false;

const child = link.getChild(name);
Expand Down Expand Up @@ -1741,7 +1744,7 @@ export class Volume {
for (const name in link.children) {
const child = link.getChild(name);

if (!child) {
if (!child || name === '.' || name === '..') {
continue;
}

Expand All @@ -1758,6 +1761,9 @@ export class Volume {

const list: TDataOut[] = [];
for (const name in link.children) {
if (name === '.' || name === '..') {
continue;
}
list.push(strToEncoding(name, options.encoding));
}

Expand Down Expand Up @@ -2798,8 +2804,8 @@ export class FSWatcher extends EventEmitter {
};

// children nodes changed
Object.values(link.children).forEach(childLink => {
if (childLink) {
Object.entries(link.children).forEach(([name, childLink]) => {
if (childLink && name !== '.' && name !== '..') {
watchLinkNodeChanged(childLink);
}
});
Expand All @@ -2814,8 +2820,8 @@ export class FSWatcher extends EventEmitter {
});

if (recursive) {
Object.values(link.children).forEach(childLink => {
if (childLink) {
Object.entries(link.children).forEach(([name, childLink]) => {
if (childLink && name !== '.' && name !== '..') {
watchLinkChildrenChanged(childLink);
}
});
Expand Down