Source

fs-file.class.ts

import { stat, Stats, access, constants, unlink, copyFile, rename } from 'fs';
import { basename, dirname } from 'path';
import { FSFileWrite } from './fs-file.write.class';
import { FSFileRead } from './fs-file.read.class';
import { FSDir } from './fs-dir.class';
import { FSFileHash } from './fs-file.hash.class';

/**
 * Options that specifies the behavior of the copyTo operation
 */
export type copyToOptions = {
  /**
   * If true, then the copy operation will fail if dest already exists.
   * COPYFILE_FICLONE_FORCE: The copy operation will attempt to create a
   * copy-on-write reflink. If the platform does not support copy-on-write, then the operation will fail.
   */
  COPYFILE_EXCL?: boolean;
  /**
   * If true then the copy operation will attempt to create a copy-on-write reflink.
   * If the platform does not support copy-on-write, then a fallback copy mechanism is used.
   */
  COPYFILE_FICLONE?: boolean;
  /**
   * If true then the copy operation will attempt to create a copy-on-write reflink.
   * If the platform does not support copy-on-write, then a fallback copy mechanism is used.
   */
  COPYFILE_FICLONE_FORCE?: boolean;
};

/**
 * Contains all methods to work with files.
 */
export class FSFile {
  constructor(public readonly path: string) {}

  /**
   * Contains file name.
   */
  public readonly name = basename(this.path);

  /**
   * Returns directory wich contains this file
   * @type {FSDir}
   */
  public readonly fsdir: FSDir = new FSDir(dirname(this.path));

  /**
   * Contains all methods for file read.
   * @type {FSFileRead}
   */
  public read: FSFileRead = new FSFileRead(this.path);

  /**
   * Contains all methods for writing file
   * @type {FSFileWrite}
   */
  public write: FSFileWrite = new FSFileWrite(this.path);

  /**
   *
   */
  public hash: FSFileHash = new FSFileHash(this.path);

  /**
   * Returns file Stats object
   * @
   * @returns {Promise<Stats>}
   */
  public async stat(): Promise<Stats> {
    return new Promise<Stats>((resolve, reject) => {
      stat(this.path, (err, stat) => {
        if (err) return reject(err);
        resolve(stat);
      });
    });
  }

  /**
   * Checks is file exits. If you need write or read file
   * use isWritable or isReadable to check if it possible.
   */
  public async isExists() {
    return new Promise<boolean>(resolve => {
      access(this.path, constants.F_OK, err => {
        if (err) return resolve(false);
        resolve(true);
      });
    });
  }

  /**
   * Checks possibility to read file.
   */
  public async isReadable() {
    return new Promise<boolean>(resolve => {
      access(this.path, constants.R_OK, err => {
        if (err) return resolve(false);
        resolve(true);
      });
    });
  }

  /**
   * Checks possibility to write into file.
   */
  public async isWritable() {
    return new Promise<boolean>(resolve => {
      access(this.path, constants.W_OK, err => {
        if (err) return resolve(false);
        resolve(true);
      });
    });
  }

  /**
   * Asynchronously removes a file.
   */
  public async unlink() {
    return new Promise<void>((resolve, reject) => {
      unlink(this.path, err => {
        if (err) return reject(err);
        resolve();
      });
    });
  }

  /**
   * Copy file. If dest is FSDir, copy file in it with source name.
   * @param {string | FSDir | FSFile} dest
   * @param {copyToOptions} options is an optional parameter that specifies the behavior of the copy operation.
   * COPYFILE_EXCL: The copy operation will fail if dest already exists.
   * COPYFILE_FICLONE: The copy operation will attempt to create a copy-on-write reflink.
   * If the platform does not support copy-on-write, then a fallback copy mechanism is used.
   * COPYFILE_FICLONE_FORCE: The copy operation will attempt to create a
   * copy-on-write reflink. If the platform does not support copy-on-write, then the operation will fail.
   * @returns {Promise<FSFile>} return FSFile of destination file
   */
  public async copyTo(dest: string | FSDir | FSFile, options: copyToOptions = {}): Promise<FSFile> {
    let destPath: string;
    if (typeof dest === 'string') {
      destPath = dest;
    } else if (dest instanceof FSDir) {
      destPath = dest.fspath[this.name]().path;
    } else if (dest instanceof FSFile) {
      destPath = dest.path;
    } else {
      throw Error("Wrong 'dest' argument. Must be string or FSDir or FSFile.");
    }
    const flags =
      (options.COPYFILE_EXCL ? constants.COPYFILE_EXCL : 0) |
      (options.COPYFILE_FICLONE ? constants.COPYFILE_FICLONE : 0) |
      (options.COPYFILE_FICLONE_FORCE ? constants.COPYFILE_FICLONE_FORCE : 0);

    return new Promise<FSFile>((resolve, reject) => {
      copyFile(this.path, destPath, flags, err => {
        if (err) return reject(err);
        if (dest instanceof FSFile) {
          resolve(dest);
        } else {
          resolve(new FSFile(destPath));
        }
      });
    });
  }

  /**
   * Moves file to destination directory
   * @param {FSDir} destDir - destination directory
   * @returns {Promise<FSFile>} - destination file
   */
  public async moveTo(destDir: FSDir): Promise<FSFile> {
    return new Promise<FSFile>((resolve, reject) => {
      const dest = destDir.fspath[this.name]().asFile();
      rename(dest.path, destDir.path, err => {
        if (err) return reject(err);
        resolve(dest);
      });
    });
  }

  /**
   * Rename file to target
   * @param {string} targetName - name of target file
   * @returns {Promise<FSFile>} - renamed file
   */
  public async rename(targetName: string): Promise<FSFile> {
    const targetFile = this.fsdir.fspath[targetName]().asFile();
    return new Promise<FSFile>((resolve, reject) => {
      rename(this.path, targetFile.path, err => {
        if (err) return reject(err);
        resolve(targetFile);
      });
    });
  }
}