All files / src/core Link.ts

98.14% Statements 53/54
85.71% Branches 12/14
92.85% Functions 13/14
98.14% Lines 53/54

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 13766x 66x       66x         66x         4936x     4936x           4936x     4936x         14359x         5721x 5721x 22x 14x   8x         4936x 4936x 4936x 4936x 4936x       4936x 4936x       169551x       3766x 3766x   3766x 1247x 1247x     3766x   3766x       6877x 6877x 6877x   6877x 6877x 3585x 3585x     6877x 6877x   6877x       1104x 1104x 66x 66x   1104x 1104x   1104x 1104x       44857x 44857x       4502x       511x       2397x                       4944x      
import { EventEmitter } from 'events';
import { constants, PATH } from '../constants';
import type { Node } from './Node';
import type { Superblock } from './Superblock';
 
const { S_IFREG } = constants;
 
/**
 * Represents a hard link that points to an i-node `node`.
 */
export class Link extends EventEmitter {
  vol: Superblock;
 
  parent: Link | undefined;
 
  children = new Map<string, Link | undefined>();
 
  // Path to this node as Array: ['usr', 'bin', 'node'].
  private _steps: string[] = [];
 
  // "i-node" of this hard link.
  node: Node;
 
  // "i-node" number of the node.
  ino: number = 0;
 
  // Number of children.
  length: number = 0;
 
  name: string;
 
  get steps() {
    return this._steps;
  }
 
  // Recursively sync children steps, e.g. in case of dir rename
  set steps(val) {
    this._steps = val;
    for (const [child, link] of this.children.entries()) {
      if (child === '.' || child === '..') {
        continue;
      }
      link?.syncSteps();
    }
  }
 
  constructor(vol: Superblock, parent: Link | undefined, name: string) {
    super();
    this.vol = vol;
    this.parent = parent;
    this.name = name;
    this.syncSteps();
  }
 
  setNode(node: Node) {
    this.node = node;
    this.ino = node.ino;
  }
 
  getNode(): Node {
    return this.node;
  }
 
  createChild(name: string, node: Node = this.vol.createNode(S_IFREG | 0o666)): Link {
    const link = new Link(this.vol, this, name);
    link.setNode(node);
 
    if (node.isDirectory()) {
      link.children.set('.', link);
      link.getNode().nlink++;
    }
 
    this.setChild(name, link);
 
    return link;
  }
 
  setChild(name: string, link: Link = new Link(this.vol, this, name)): Link {
    this.children.set(name, link);
    link.parent = this;
    this.length++;
 
    const node = link.getNode();
    if (node.isDirectory()) {
      link.children.set('..', 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()) {
      link.children.delete('..');
      this.getNode().nlink--;
    }
    this.children.delete(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();
    return this.children.get(name);
  }
 
  getPath(): string {
    return this.steps.join(PATH.SEP);
  }
 
  getParentPath(): string {
    return this.steps.slice(0, -1).join(PATH.SEP);
  }
 
  getName(): string {
    return this.steps[this.steps.length - 1];
  }
 
  toJSON() {
    return {
      steps: this.steps,
      ino: this.ino,
      children: Array.from(this.children.keys()),
    };
  }
 
  syncSteps() {
    this.steps = this.parent ? this.parent.steps.concat([this.name]) : [this.name];
  }
}