All files / src/core Link.ts

98.18% Statements 54/55
91.66% Branches 11/12
92.85% Functions 13/14
98.18% Lines 54/55

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 144 14567x 67x                   67x         67x 3188x           3188x     3188x           3188x     3188x         6392x         3256x 3256x 22x 14x   8x         3188x 3188x 3188x 3188x       3188x 3188x       63494x       2262x 2262x   2262x 930x 930x     2262x   2262x       4168x 4168x 4168x   4168x 4168x 2780x 2780x     4168x 4168x   4168x       105x 105x 34x 34x   105x 105x   105x 105x       15687x 15687x       2290x       477x 477x       647x                       3196x      
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 {
    const parent = this.steps.slice(0, -1).join(PATH.SEP);
    return parent ? parent : 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];
  }
}