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 137 138 139 140 141 142 143 14465x 65x                   65x         65x 3013x           3013x     3013x           3013x     3013x         6006x         3074x 3074x 22x 14x   8x         3013x 3013x 3013x 3013x       3013x 3013x       60440x       2137x 2137x   2137x 882x 882x     2137x   2137x       3936x 3936x 3936x   3936x 3936x 2632x 2632x     3936x 3936x   3936x       98x 98x 34x 34x   98x 98x   98x 98x       14954x 14954x       2172x       441x       597x                       3021x      
import { constants, PATH } from '../constants';
import { FanOut } from 'thingies/lib/fanout';
import type { Node } from './Node';
import type { Superblock } from './Superblock';
 
export type LinkEventChildAdd = [type: 'child:add', link: Link, parent: Link];
 
export type LinkEventChildDelete = [type: 'child:del', link: Link, parent: Link];
 
export type LinkEvent = LinkEventChildAdd | LinkEventChildDelete;
 
const { S_IFREG } = constants;
 
/**
 * Represents a hard link that points to an i-node `node`.
 */
export class Link {
  public readonly changes = new FanOut<LinkEvent>();
 
  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) {
    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.changes.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.changes.emit(['child:del', link, this]);
  }
 
  getChild(name: string): Link | undefined {
    this.getNode().atime = 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];
  }
}