All files / src/fsa-to-node FsaNodeCore.ts

74.28% Statements 78/105
67.44% Branches 29/43
75% Functions 12/16
76.28% Lines 74/97

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 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 2102x 2x 2x   2x   2x         2x 2x 299x     299x 299x   299x                                     299x     611x 611x               3564x 3564x 3564x 3564x 3247x     6x 6x   2x   4x         3558x                 1181x 1181x 934x               1532x 1530x 1524x 1524x 234x   1290x 1290x   746x 746x                       544x               859x 859x 859x 855x       263x       345x             5x 5x 5x 5x 5x       661x 661x 661x 283x 283x 43x     283x 283x 43x 43x             43x       618x 618x 618x 611x   7x 7x       7x                         611x 611x 611x 611x       257x 256x 256x 256x                        
import { createError, isFd, pathToFilename } from '../node/util';
import { pathToLocation } from './util';
import { ERRSTR } from '../node/constants';
import { FsaToNodeConstants } from './constants';
import { FsaNodeFsOpenFile } from './FsaNodeFsOpenFile';
import { FLAG } from '../consts/FLAG';
import * as util from '../node/util';
import type * as fsa from '../fsa/types';
import type * as misc from '../node/types/misc';
import type { FsaNodeSyncAdapter } from './types';
 
export class FsaNodeCore {
  protected static fd: number = 0x7fffffff;
  protected readonly fds = new Map<number, FsaNodeFsOpenFile>();
 
  public constructor(
    protected readonly root: fsa.IFileSystemDirectoryHandle | Promise<fsa.IFileSystemDirectoryHandle>,
    public syncAdapter?: FsaNodeSyncAdapter,
  ) {
    Iif (root instanceof Promise) {
      root
        .then(root => {
          (this as any).root = root;
        })
        .catch(error => {});
    }
  }
 
  protected getSyncAdapter(): FsaNodeSyncAdapter {
    const adapter = this.syncAdapter;
    Iif (!adapter) throw new Error('No sync adapter');
    return adapter;
  }
 
  /**
   * A list of reusable (opened and closed) file descriptors, that should be
   * used first before creating a new file descriptor.
   */
  releasedFds: number[] = [];
 
  protected newFdNumber(): number {
    const releasedFd = this.releasedFds.pop();
    return typeof releasedFd === 'number' ? releasedFd : FsaNodeCore.fd--;
  }
 
  /**
   * @param path Path from root to the new folder.
   * @param create Whether to create the folders if they don't exist.
   */
  protected async getDir(path: string[], create: boolean, funcName?: string): Promise<fsa.IFileSystemDirectoryHandle> {
    let curr: fsa.IFileSystemDirectoryHandle = await this.root;
    const options: fsa.GetDirectoryHandleOptions = { create };
    try {
      for (const name of path) {
        curr = await curr.getDirectoryHandle(name, options);
      }
    } catch (error) {
      if (error && typeof error === 'object') {
        switch (error.name) {
          case 'TypeMismatchError':
            throw createError('ENOTDIR', funcName, path.join(FsaToNodeConstants.Separator));
          case 'NotFoundError':
            throw createError('ENOENT', funcName, path.join(FsaToNodeConstants.Separator));
        }
      }
      throw error;
    }
    return curr;
  }
 
  protected async getFile(
    path: string[],
    name: string,
    funcName?: string,
    create?: boolean,
  ): Promise<fsa.IFileSystemFileHandle> {
    const dir = await this.getDir(path, false, funcName);
    const file = await dir.getFileHandle(name, { create });
    return file;
  }
 
  protected async getFileOrDir(
    path: string[],
    name: string,
    funcName?: string,
  ): Promise<fsa.IFileSystemFileHandle | fsa.IFileSystemDirectoryHandle> {
    const dir = await this.getDir(path, false, funcName);
    if (!name) return dir;
    try {
      const file = await dir.getFileHandle(name);
      return file;
    } catch (error) {
      if (error && typeof error === 'object') {
        switch (error.name) {
          case 'TypeMismatchError':
            try {
              return await dir.getDirectoryHandle(name);
            } catch (error2) {
              Iif (error2 && typeof error2 === 'object') {
                switch (error2.name) {
                  case 'TypeMismatchError':
                    throw createError('ENOTDIR', funcName, path.join(FsaToNodeConstants.Separator));
                  case 'NotFoundError':
                    throw createError('ENOENT', funcName, path.join(FsaToNodeConstants.Separator));
                }
              }
            }
          case 'NotFoundError':
            throw createError('ENOENT', funcName, path.join(FsaToNodeConstants.Separator));
        }
      }
      throw error;
    }
  }
 
  protected getFileByFd(fd: number, funcName?: string): FsaNodeFsOpenFile {
    Iif (!isFd(fd)) throw TypeError(ERRSTR.FD);
    const file = this.fds.get(fd);
    if (!file) throw createError('EBADF', funcName);
    return file;
  }
 
  protected async getFileByFdAsync(fd: number, funcName?: string): Promise<FsaNodeFsOpenFile> {
    return this.getFileByFd(fd, funcName);
  }
 
  public async __getFileById(id: misc.TFileId, funcName?: string): Promise<fsa.IFileSystemFileHandle> {
    if (typeof id === 'number') return (await this.getFileByFd(id, funcName)).file;
    const filename = pathToFilename(id);
    const [folder, name] = pathToLocation(filename);
    return await this.getFile(folder, name, funcName);
  }
 
  protected async getFileByIdOrCreate(id: misc.TFileId, funcName?: string): Promise<fsa.IFileSystemFileHandle> {
    Iif (typeof id === 'number') return (await this.getFileByFd(id, funcName)).file;
    const filename = pathToFilename(id);
    const [folder, name] = pathToLocation(filename);
    const dir = await this.getDir(folder, false, funcName);
    return await dir.getFileHandle(name, { create: true });
  }
 
  protected async __open(filename: string, flags: number, mode: number): Promise<FsaNodeFsOpenFile> {
    const [folder, name] = pathToLocation(filename);
    const throwIfExists = !!(flags & FLAG.O_EXCL);
    if (throwIfExists) {
      try {
        await this.getFile(folder, name, 'open', false);
        throw util.createError('EEXIST', 'writeFile');
      } catch (error) {
        const file404 =
          error && typeof error === 'object' && (error.code === 'ENOENT' || error.name === 'NotFoundError');
        if (!file404) {
          if (error && typeof error === 'object') {
            switch (error.name) {
              case 'TypeMismatchError':
                throw createError('ENOTDIR', 'open', filename);
              case 'NotFoundError':
                throw createError('ENOENT', 'open', filename);
            }
          }
          throw error;
        }
      }
    }
    try {
      const createIfMissing = !!(flags & FLAG.O_CREAT);
      const fsaFile = await this.getFile(folder, name, 'open', createIfMissing);
      return this.__open2(fsaFile, filename, flags, mode);
    } catch (error) {
      if (error && typeof error === 'object') {
        switch (error.name) {
          case 'TypeMismatchError':
            throw createError('ENOTDIR', 'open', filename);
          case 'NotFoundError':
            throw createError('ENOENT', 'open', filename);
        }
      }
      throw error;
    }
  }
 
  protected __open2(
    fsaFile: fsa.IFileSystemFileHandle,
    filename: string,
    flags: number,
    mode: number,
  ): FsaNodeFsOpenFile {
    const fd = this.newFdNumber();
    const file = new FsaNodeFsOpenFile(fd, mode, flags, fsaFile, filename);
    this.fds.set(fd, file);
    return file;
  }
 
  protected async __close(fd: number): Promise<void> {
    const openFile = await this.getFileByFdAsync(fd, 'close');
    await openFile.close();
    const deleted = this.fds.delete(fd);
    if (deleted) this.releasedFds.push(fd);
  }
 
  protected getFileName(id: misc.TFileId): string {
    Iif (typeof id === 'number') {
      const openFile = this.fds.get(id);
      Iif (!openFile) throw createError('EBADF', 'readFile');
      return openFile.filename;
    }
    return pathToFilename(id);
  }
}