UNPKG

wranglebot

Version:

open source media asset management

582 lines (512 loc) 14.2 kB
import os from "os"; import path from "path"; import fs from "fs"; import si from "systeminformation"; import ejectMedia from "eject-media"; import checkDiskSpace from "check-disk-space"; import { sync as writeFileAtomicSync } from "write-file-atomic"; import LogBot from "logbotjs"; import Cryptr from "cryptr"; import { exec } from "child_process"; import config from "./Config.js"; class Finder { cryptr = new Cryptr("b2909139-4cdc-46d6-985c-3726ede95335"); //this is just for obfuscation supportedPlatforms: { darwin: string; linux: string } = { darwin: "MacOS", linux: "linux", }; platform: NodeJS.Platform = os.platform(); pathToVolumes: string = "/"; constructor() { if (this.supportedPlatforms[this.platform]) { LogBot.log(200, "Detected supported Platform: " + this.supportedPlatforms[this.platform]); } else { throw new Error("Detected a non supported Platform. Please start this app on a supported Platform: " + Object.values(this.supportedPlatforms)); } switch (this.platform) { case "darwin": this.pathToVolumes = "/volumes/"; break; case "linux": this.pathToVolumes = "/media/" + os.userInfo().username + "/"; break; } } isMac() { return this.platform === "darwin"; } isLinux() { return this.platform === "linux"; } openInFinder(path, callback) { if (this.isMac()) { exec("open '" + path + "'", callback); } else if (this.isLinux()) { exec("xdg-open '" + path + "'", callback); } } /** * @typedef {Object} DriveOptions * @property {string} label * @property {string} path * @property {string} serialNumber * @property {string} interface * @property {boolean} removable */ /** * * @return {Promise<DriveOptions>} */ getDisks(): Promise<any[]> { return new Promise((resolve) => { si.fsSize().then((r) => { let blockDevices = r.filter((element) => { return element.mount.toLowerCase().startsWith(this.pathToVolumes.toLowerCase()); }); blockDevices = blockDevices.map((d) => { d = Object.assign(d, { partition: d.type, type: undefined, interface: "software", serialNumber: "NaN", host: "Generic Host Controller", }); return d; }); si.diskLayout().then((diskLayout) => { for (let device of blockDevices) { for (let disk of diskLayout) { if ( device.fs.includes(disk.device) || // if the device is a disk (device.mount === "/" && disk.device === "disk0") //is this a macos disk? ) { device = Object.assign(device, { type: disk.type, host: disk.name, serialNumber: disk.serialNum, interface: disk.interfaceType, }); break; } } } resolve(blockDevices); }); }); }); } getMountPoint(pathToElement) { const elements = pathToElement.split("/"); for (let el of elements) { if (el === "") elements.splice(elements.indexOf(el), 1); } return "/" + elements[0] + "/" + elements[1]; } /** * Retrieve all folders with subfolders within a folder * * @param {string} sourcePath * @param {number} limit max level of subfolders * @param {number} index * @return {String[]} the array of absolute folder paths */ getFolders(sourcePath, limit, index = 0) { let matches: any = []; const paths = this.getContentOfFolder(sourcePath); for (let path of paths) { const fullPath = this.join(sourcePath, path); if (this.existsSync(fullPath) && this.isDirectory(fullPath) && path.charAt(0) !== "." && path.indexOf(".app") === -1) { if (index <= limit) { matches = [...matches, fullPath, ...this.getFolders(fullPath, limit, index + 1)]; } else { matches.push(fullPath); } } } return matches; } getPathToUserData(subPath = "") { return path.join(config.getPathToUserData(),subPath) } /** * * @param pathToElement * @return {boolean} */ access(pathToElement) { try { fs.accessSync(pathToElement.toString()); return true; } catch (e) { return false; } } /** * Returns whether a path is reachable for this app * * @param path * @returns {boolean} */ isReachable(path) { try { const elements = path.split("/"); for (let el of elements) { if (el === "") elements.splice(elements.indexOf(el), 1); } //works for unix and macos return this.existsSync("/" + elements[0] + "/" + elements[1]); } catch (e) { console.error(e); return false; } } /** * Returns whether a path exists, can be file or folder * use isDirectory to check for type * * @param pathToElement * @returns {boolean} */ existsSync(pathToElement) { return fs.existsSync(pathToElement); } /** * returns if a file is in the home directory * @param pathToElement * @returns {boolean} */ exists(pathToElement) { return fs.existsSync(this.join(this.getPathToUserData(), pathToElement)); } check(...elements) { const path = this.join(...elements); return this.existsSync(path); } /** * Creates a new folder * * @param pathToNewFolder * @param options? {object|undefined} * @returns {boolean} */ mkdirSync(pathToNewFolder, options = {}) { try { fs.mkdirSync(pathToNewFolder, options); return true; } catch (e) { return false; } } /** * Gets file stats * @deprecated use lstatSync instead * @param pathToElement * @returns {*} */ statSync(pathToElement) { return fs.statSync(pathToElement); } /** * Gets file stats * @param pathToElement * @returns {*} */ lstatSync(pathToElement) { return fs.lstatSync(pathToElement); } createReadStream(pathToElement, options) { return fs.createReadStream(pathToElement, options); } createWriteStream(pathToElement, options = {}) { return fs.createWriteStream(pathToElement, options); } readdirSync(pathToFolder) { return fs.readdirSync(pathToFolder); } /** * Reads a File * * @param {string} pathToElement */ async readFile(pathToElement) { return new Promise((resolve) => { const readStream = this.createReadStream(pathToElement, { encoding: "utf8" }); let file = ""; readStream.on("data", (chunk) => { file += chunk; }); readStream.on("end", () => { resolve(JSON.parse(file)); }); }); } /** * Writes to a file * @param pathToNewElement * @param content * @param callback */ writeFile(pathToNewElement, content, callback) { fs.writeFile(pathToNewElement, content, callback); } /** * Writes to a file synchronously * @param pathToElement * @param content * @param options? */ writeFileSync(pathToElement, content, options = undefined) { this.mkdirSync(this.dirname(pathToElement), { recursive: true }); return writeFileAtomicSync(pathToElement, content, options); } save(fileName, content, encrypt = false) { try { let data = JSON.stringify(content); if (encrypt) { data = this.encrypt(data); } this.writeFileSync(this.join(this.getPathToUserData(), fileName), data); return true; } catch (e) { console.error("Failed to save file", e); throw e; } } saveAsync(fileName, content, encrypt = false) { return new Promise((resolve, reject) => { let data = JSON.stringify(content); if (encrypt) { data = this.encrypt(data); } const createWriteStream = fs.createWriteStream(this.join(this.getPathToUserData(), fileName)); createWriteStream.write(data); createWriteStream.end(); createWriteStream.on("finish", () => { createWriteStream.close(); resolve(true); }); createWriteStream.on("error", (e) => { createWriteStream.close(); reject(e); }); }); } encrypt(data) { return this.cryptr.encrypt(data); } decrypt(data) { return this.cryptr.decrypt(data); } load(fileName, decrypt = false) { try { let contentOfFile = this.readFileSync(this.join(this.getPathToUserData(), fileName)).toString(); if (decrypt) { contentOfFile = this.decrypt(contentOfFile); } return JSON.parse(contentOfFile); } catch (e) { console.error("Failed to read file " + fileName); throw e; } } /** * Reads a File * * @param pathToElement * @returns {Buffer} */ readFileSync(pathToElement) { return fs.readFileSync(pathToElement); } parseFileSync(pathToElement) { return JSON.parse(this.readFileSync(pathToElement).toString()); } rmSync(pathToElementToRemove) { return fs.rmSync(pathToElementToRemove, { recursive: true, force: true }); } basename(pathToElement) { return path.basename(pathToElement); } label(pathToElement) { return this.basename(pathToElement).split(".")[0]; } extname(pathToElement) { return path.extname(pathToElement); } dirname(pathToElement) { return path.dirname(pathToElement); } join(...paths) { return path.join(...paths); } watch(pathToFolder, callback) { return fs.watch(pathToFolder, callback); } async checkDiskSpace(pathToDevice) { return new Promise((resolve) => { checkDiskSpace(pathToDevice) .then((diskSpace) => { resolve(diskSpace); }) .catch((err) => { resolve({}); }); }); } /** * ejects a drive * * @param pathToDevice * @param callback */ eject(pathToDevice, callback) { ejectMedia.eject(pathToDevice, callback); } /** * Returns the file type of the given path * * @param filename {string} the path to the file * @returns {'photo'|'video'|'audio'|'sidecar'} */ getFileType(filename) { let type = this.extname(filename); if ( type.match( /(webm)|(mkv)|(vob)|(ogv)|(ogg)|(gif)|(gifv)|(avi)|(mts)|(m2ts)|(ts)|(mov)|(qt)|(wmv)|(mp4)|(m4p)|(mpeg)|(m4v)|(mpg)|(mpg2)|(mpe)|(mpv)|(m2v)|(3gp)|(3gp2)|(mxf)|(flv)/gim ) ) { return "video"; } if (type.match(/(r3d)|(braw)/gim)) { return "video-raw"; } if ( type.match( /(aa)|(aac)|(aax)|(act)|(aiff)|(alac)|(amr)|(ape)|(au)|(awb)|(dvf)|(flac)|(m4a)|(m4b)|(m4p)|(mp3)|(mpc)|(msv)|(nmf)|(ogg)|(oga)|(mogg)|(opus)|(wav)|(wma)/gim ) ) { return "audio"; } if (type.match(/(png)|(bmp)|(gif)|(gifv)|(avif)|(jpeg)|(jpg)|(svg)|(webp)/gim)) { return "photo"; } return "sidecar"; } /** * Returns the items of a folder * @param {String} pathToFolder Absolute Path to Folder * @param options */ getContentOfFolder( pathToFolder, options: { showHidden: boolean; filters: "both" | "files" | "folders"; recursive: boolean; depth: Number } = { showHidden: false, filters: "both", recursive: false, depth: 10, } ) { try { let list = fs.readdirSync(pathToFolder); if (!options.showHidden) { //remove all files that start with a dot list = list.filter((item) => !item.startsWith(".")); } if (options.filters === "files") { list = list.filter((item) => !this.isDir(pathToFolder, item)); } if (options.filters === "folders") { list = list.filter((item) => this.isDir(pathToFolder, item)); } return list; } catch (e) { return []; } } /** * Returns true if the given path is a folder * @param path * @returns {false|*} */ isDirectory(path) { return this.isDir(path); } /** * Checks if the path is a directory * @param elements * @returns {false|*} */ isDir(...elements) { const path = this.join(...elements); try { //check if the path exists fs.accessSync(path, fs.constants.F_OK); //check if the path is a directory return fs.lstatSync(path).isDirectory(); } catch (e) { return false; } } /** * Rename a file or folder * * @param pathToElement * @param newName */ rename(pathToElement, newName) { const newPath = this.join(this.dirname(pathToElement), newName + this.extname(pathToElement)); return fs.renameSync(pathToElement, newPath); } copy(pathToElement, newPath) { return fs.copyFileSync(pathToElement, newPath); } /** * Move a file or folder * * @param pathToElement * @param newFolder */ move(pathToElement, newFolder) { return fs.renameSync(pathToElement, this.join(newFolder, this.basename(pathToElement))); } /** * Rename a file and move it, returns true if the file was moved * * @param pathToElement * @param newName * @param newFolder * @returns {boolean} */ renameAndMove(pathToElement, newName, newFolder) { const newPath = this.join(newFolder, newName + this.extname(pathToElement)); fs.renameSync(pathToElement, newPath); return fs.existsSync(newPath); } getVolumePath(stringToParse) { const volumeName = this.getVolumeName(stringToParse); return this.join(this.pathToVolumes, volumeName); } getVolumeName(pathToElement) { try { let path = pathToElement.split("/"); let element = path[1].toLowerCase(); //macos if (this.platform === "darwin") { if (element === "volumes") return path[2]; if (element === "users") return "Macintosh HD"; } //linux if (this.platform === "linux") { if (element === "media") return path[2]; if (element === "home") { return "/"; } } throw new Error("incompatible path to a volume"); } catch (e) { throw e; } } } const finder = new Finder(); export default finder;