wkdhkr/dedupper

View on GitHub
src/helpers/FileNameMarkHelper.js

Summary

Maintainability
A
3 hrs
Test Coverage
// @flow
import fs from "fs-extra";
import path from "path";
import {
  MARK_BLOCK,
  MARK_ERASE,
  MARK_HOLD,
  MARK_DEDUPE,
  MARK_SAVE,
  MARK_REPLACE,
  MARK_TRANSFER
} from "../types/FileNameMarks";
import type { FileNameMark } from "../types/FileNameMarks";

function escapeRegExp(str: string): string {
  return str.replace(/[-[\]/{}()*+?.\\^$|]/g, "\\$&");
}

export default class FileNameMarkHelper {
  static CHAR_BLOCK: "b" = "b";

  static CHAR_ERASE: "e" = "e";

  static CHAR_HOLD: "h" = "h";

  static CHAR_DEDUPE: "d" = "d";

  static CHAR_SAVE: "s" = "s";

  static CHAR_REPLACE: "r" = "r";

  static CHAR_TRANSFER: "t" = "t";

  static DIR_ERASE: "!erase" = "!erase";

  static DIR_BLOCK: "!block" = "!block";

  static DIR_DEDUPE: "!dedupe" = "!dedupe";

  static DIR_SAVE: "!save" = "!save";

  static DIR_REPLACE: "!replace" = "!replace";

  static DIR_TRANSFER: "!transfer" = "!transfer";

  static markToCharLookup: { [FileNameMark]: string } = {
    [MARK_BLOCK]: FileNameMarkHelper.CHAR_BLOCK,
    [MARK_ERASE]: FileNameMarkHelper.CHAR_ERASE,
    [MARK_DEDUPE]: FileNameMarkHelper.CHAR_DEDUPE,
    [MARK_HOLD]: FileNameMarkHelper.CHAR_HOLD,
    [MARK_SAVE]: FileNameMarkHelper.CHAR_SAVE,
    [MARK_REPLACE]: FileNameMarkHelper.CHAR_REPLACE,
    [MARK_TRANSFER]: FileNameMarkHelper.CHAR_TRANSFER
  };

  static charToMarkLookup: { [string]: FileNameMark } = {
    [FileNameMarkHelper.CHAR_BLOCK]: MARK_BLOCK,
    [FileNameMarkHelper.CHAR_ERASE]: MARK_ERASE,
    [FileNameMarkHelper.CHAR_DEDUPE]: MARK_DEDUPE,
    [FileNameMarkHelper.CHAR_HOLD]: MARK_HOLD,
    [FileNameMarkHelper.CHAR_SAVE]: MARK_SAVE,
    [FileNameMarkHelper.CHAR_REPLACE]: MARK_REPLACE,
    [FileNameMarkHelper.CHAR_TRANSFER]: MARK_TRANSFER
  };

  static MARK_PREFIX: string = "!";

  static async isExists(targetPath: string): Promise<boolean> {
    if (await fs.pathExists(targetPath)) {
      return true;
    }
    return Boolean(await FileNameMarkHelper.findMarkedFile(targetPath));
  }

  static extractNumber(targetPath: string): ?number {
    const { name } = path.parse(targetPath);
    const { ext } = path.parse(name);
    const match = ext.match(/[0-9]+/);
    if (match) {
      return parseInt(match[0], 10);
    }
    return null;
  }

  static async findMarkedFile(targetPath: string): Promise<?string> {
    try {
      const stripedPath = FileNameMarkHelper.strip(targetPath);
      const { dir, name, ext } = path.parse(stripedPath);

      const regEx = new RegExp(`${escapeRegExp(`${name}.!`)}.*${ext}$`);
      const files = (await fs.readdir(dir)).filter(f => f.match(regEx));
      if (files.length) {
        return files[0];
      }
      return null;
    } catch (e) {
      return null;
    }
  }

  static async findReplaceFileByNumber(
    targetPath: string,
    n: ?number
  ): Promise<?string> {
    if (!n) {
      return null;
    }

    const stripedPath = FileNameMarkHelper.strip(targetPath);
    const { dir, name } = path.parse(stripedPath);

    const regEx = new RegExp(`${escapeRegExp(`${name}#${n}.`)}`);
    const files = (await fs.readdir(dir)).filter(f => f.match(regEx));
    if (files.length) {
      return files[0];
    }
    return null;
  }

  static async findReplaceFile(targetPath: string): Promise<?string> {
    const symLinkPath = await FileNameMarkHelper.findReplaceFileByNumber(
      targetPath,
      FileNameMarkHelper.extractNumber(targetPath)
    );
    if (!symLinkPath) {
      return null;
    }
    try {
      const destPath = await fs.readlink(symLinkPath);
      await fs.stat(destPath);
      return destPath;
    } catch (e) {
      return null;
    }
  }

  static detectMarksByDirName: (dirName: string) => Set<FileNameMark> = (
    dirName: string
  ): Set<FileNameMark> => {
    if (dirName === FileNameMarkHelper.DIR_DEDUPE) {
      return new Set([MARK_DEDUPE]);
    }
    if (dirName === FileNameMarkHelper.DIR_SAVE) {
      return new Set([MARK_SAVE]);
    }
    if (dirName === FileNameMarkHelper.DIR_REPLACE) {
      return new Set([MARK_REPLACE]);
    }
    if (dirName === FileNameMarkHelper.DIR_TRANSFER) {
      return new Set([MARK_TRANSFER]);
    }
    if (dirName === FileNameMarkHelper.DIR_BLOCK) {
      return new Set([MARK_BLOCK]);
    }
    if (dirName === FileNameMarkHelper.DIR_ERASE) {
      return new Set([MARK_ERASE]);
    }
    return new Set([]);
  };

  static extract(targetPath: string): Set<FileNameMark> {
    const { dir, name } = path.parse(targetPath);
    const { ext } = path.parse(name);
    // torrent?
    if (ext === ".!ut") {
      return new Set([]);
    }

    // check parents dir
    const marks = new Set(
      [].concat(
        ...dir
          .split(path.sep)
          .map(FileNameMarkHelper.detectMarksByDirName, FileNameMarkHelper)
          .map(s => Array.from(s))
      )
    );
    if (marks.size) {
      return marks;
    }

    if (ext.startsWith(`.${FileNameMarkHelper.MARK_PREFIX}`)) {
      [...ext].forEach(c => {
        if (FileNameMarkHelper.charToMarkLookup[c]) {
          marks.add(FileNameMarkHelper.charToMarkLookup[c]);
        }
      });
      return marks;
    }
    return new Set([]);
  }

  static mark(
    targetPath: string,
    marks: Set<FileNameMark>,
    sortMarks?: string[]
  ): string {
    if (marks.size === 0) {
      return targetPath;
    }
    const { dir, name, ext } = path.parse(FileNameMarkHelper.strip(targetPath));
    const sortMark = (sortMarks || [])
      .map(m => `[${FileNameMarkHelper.MARK_PREFIX}_${m}]`)
      .join("");
    return path.join(
      dir,
      sortMark + name + FileNameMarkHelper.createToken(marks) + ext
    );
  }

  static createToken(marks: Set<FileNameMark>): string {
    const chars = [];
    marks.forEach(m => {
      if (FileNameMarkHelper.markToCharLookup[m]) {
        chars.push(FileNameMarkHelper.markToCharLookup[m]);
        return;
      }
      throw new Error(`unknown mark detected. mark = ${m}`);
    });
    if (chars.length) {
      return `.${FileNameMarkHelper.MARK_PREFIX}${chars.join("")}`;
    }
    return "";
  }

  static strip(targetPath: string): string {
    const { dir, name, ext } = path.parse(targetPath);
    const { name: originalName, ext: markToken } = path.parse(name);

    let stripedPath = targetPath;
    if (markToken.startsWith(`.${FileNameMarkHelper.MARK_PREFIX}`)) {
      stripedPath = path.join(dir, originalName + ext);
    }
    const sortMarkRegEx = new RegExp(
      `\\[\\${FileNameMarkHelper.MARK_PREFIX}_.*?\\]`,
      "g"
    );
    return stripedPath
      .replace(sortMarkRegEx, "")
      .replace(FileNameMarkHelper.DIR_ERASE + path.sep, "")
      .replace(FileNameMarkHelper.DIR_DEDUPE + path.sep, "")
      .replace(FileNameMarkHelper.DIR_SAVE + path.sep, "")
      .replace(FileNameMarkHelper.DIR_TRANSFER + path.sep, "")
      .replace(FileNameMarkHelper.DIR_REPLACE + path.sep, "")
      .replace(FileNameMarkHelper.DIR_BLOCK + path.sep, "");
  }
}