diff --git a/README.md b/README.md index e220188..8c73215 100644 --- a/README.md +++ b/README.md @@ -117,3 +117,25 @@ const stream = new WritableBuffer(); sourceStream.pipe(stream); sourceStream.on('end', () => useData(stream.get())); ``` + + +## utils.js + +* `read` - (async) Reads a whole file to string, NOT A Buffer. +* `write` - (async) Write a file. +* `copy` - (async) Copy a file. +* `exists` - (async) Check if a file/folder exists. +* `mkdir` - (async) Create an empty folder. +* `stat` - (async) Get status on a file. +* `isDir` - (async) Check if the path is a folder. +* `isFile` - (async) Check if the path is a file. +* `dirUp` - Cut the path one folder up. +* `ensuredir` - (async) Like `mkdir -p`, makes sure a directory exists. +* `copysafe` - (async) Copy a file, `dest` folder is created if needed. +* `readdir` - (async) Get file/folder names of the 1st level. +* `subdirs` - (async) Get folder paths (concatenated with input) of the 1st level. +* `subfiles` - (async) Get file paths (concatenated with input) of the 1st level. +* `traverse` - (async) Get all nested files recursively. +* `copyall` - (async) Copy a folder with all the contained files. +* `rmdir` - (async) Like `rm -rf`, removes everything recursively. +* `rm` - (async) Remove a file. Must be a file, not a folder. Just `fs.unlink`. diff --git a/install.js b/install.js index b97910e..16ea014 100644 --- a/install.js +++ b/install.js @@ -8,6 +8,7 @@ const fs = require('fs'); const AdmZip = require('adm-zip'); const { bin, platform } = require('.'); +const { mkdir, rm } = require('./utils'); const protocols = { http, https }; @@ -20,11 +21,16 @@ const onError = msg => { const zipPath = `${bin}/${bin}.zip`; -const install = (url, count = 1) => { - - const proto = protocols[url.match(/^https?/)[0]]; - - const request = proto.get(url, response => { +const install = async (url, count = 1) => { + try { + + const proto = protocols[url.match(/^https?/)[0]]; + + const response = await new Promise((res, rej) => { + const request = proto.get(url, response => res(response)); + request.on('error', err => rej(err)); + }); + response.on('error', err => { throw err; }); // Handle redirects if ([301, 302, 303, 307].includes(response.statusCode)) { @@ -32,36 +38,37 @@ const install = (url, count = 1) => { return install(response.headers.location, count + 1); } console.log(url); - return onError('Error: Too many redirects.'); + throw new Error('Error: Too many redirects.'); } // Handle bad status if (response.statusCode !== 200) { console.log(url); - return onError(`Response status was ${response.statusCode}`); + throw new Error(`Response status was ${response.statusCode}`); } - response.on('error', err => onError(err.message)); + await mkdir(bin); - fs.mkdirSync(bin); - const zipWriter = fs.createWriteStream(zipPath); - zipWriter.on('error', err => onError(err.message)); - response.pipe(zipWriter); - - zipWriter.on('finish', () => { - const zip = new AdmZip(zipPath); - zip.extractAllTo(bin, true); - fs.unlinkSync(zipPath); + await new Promise((res, rej) => { + const zipWriter = fs.createWriteStream(zipPath); + zipWriter.on('error', err => rej(err)); + zipWriter.on('finish', () => res()); + response.pipe(zipWriter); }); - }); - - request.on('error', err => onError(err.message)); + const zip = new AdmZip(zipPath); + zip.extractAllTo(bin, true); + + await rm(zipPath); + + } catch (ex) { + onError(ex.message); + } }; module.exports = folder => { const url = `${folder}/${platform}.zip`; - install(url); + install(url).then(); }; diff --git a/utils.js b/utils.js new file mode 100644 index 0000000..49a226a --- /dev/null +++ b/utils.js @@ -0,0 +1,195 @@ +'use strict'; + +const fs = require('fs'); + +// (async) Reads a whole file to string, NOT A Buffer +const read = name => new Promise( + (res, rej) => fs.readFile( + name, + (err, data) => (err ? rej(err) : res(data.toString())) + ) +); + + +// (async) Write a file +const write = (name, text) => new Promise( + (res, rej) => fs.writeFile(name, text, err => (err ? rej(err) : res())) +); + + +// (async) Copy a file +const copy = async (src, dest) => { + try { + await new Promise( + (res, rej) => fs.copyFile(src, dest, err => (err ? rej(err) : res())) + ); + } catch (e) { + if (e.code !== 'EBUSY') { + console.warn('WARNING\n', e); + } + } +}; + + +// (async) Check if a file/folder exists +const exists = name => new Promise( + res => fs.access( + name, + fs.constants.F_OK, + err => res(err ? false : true) + ) +); + + +// (async) Create an empty folder +const mkdir = async name => { + if (await exists(name)) { + return; + } + return new Promise( + (res, rej) => fs.mkdir(name, err => (err ? rej(err) : res())) + ); +}; + + +// (async) Get status on a file +const stat = name => new Promise( + (res, rej) => fs.stat(name, (err, stats) => (err ? rej(err) : res(stats))) +); + + +// (async) Check if the path is a folder +const isDir = async name => (await stat(name)).isDirectory(); + + +// (async) Check if the path is a file +const isFile = async name => (await stat(name)).isFile(); + + +// Cut the path one folder up +const dirUp = dir => dir.replace(/\\/g, '/').split('/').slice(0, -1).join('/'); + + +// (async) Like `mkdir -p`, makes sure a directory exists +const ensuredir = async dir => { + if (await exists(dir) && await isDir(dir)) { + return; + } + await ensuredir(dirUp(dir)); + await mkdir(dir); +}; + + +// (async) Copy a file, `dest` folder is created if needed +const copysafe = async (src, dest) => { + await ensuredir(dirUp(dest)); + await copy(src, dest); +}; + + +// (async) Get file/folder names of the 1st level +const readdir = name => new Promise( + (res, rej) => fs.readdir( + name, + (err, dirents) => (err ? rej(err) : res(dirents)) + ) +); + + +// (async) Get folder paths (concatenated with input) of the 1st level +const subdirs = async name => { + const all = await readdir(name); + const mapped = await Promise.all(all.map(d => isDir(`${name}/${d}`))); + return all.filter((_, i) => mapped[i]); +}; + + +// (async) Get file paths (concatenated with input) of the 1st level +const subfiles = async name => { + const all = await readdir(name); + const mapped = await Promise.all(all.map(d => isFile(`${name}/${d}`))); + return all.filter((_, i) => mapped[i]).map(f => `${name}/${f}`); +}; + + +// (async) Get all nested files recursively +// Folder paths are omitted by default +// Order is: shallow-to-deep, each subdirectory lists dirs-then-files. +const traverse = async (name, showDirs = false) => { + const dirs = []; + const stack = [name]; + while (stack.length) { + const dir = stack.pop(); + dirs.push(dir); + (await subdirs(dir)).forEach(d => stack.push(`${dir}/${d}`)); + } + return (showDirs ? dirs : []).concat( + ...(await Promise.all(dirs.map(subfiles))) + ); +}; + + +// (async) Copy a folder with all the contained files +const copyall = async (src, dest) => { + const files = (await traverse(src, true)).reverse(); + while (files.length) { + const target = files.pop(); + const dir = await isDir(target); + if (dir) { + await mkdir(target.replace(src, dest)); + } else { + await copy(target, target.replace(src, dest)); + } + } +}; + + +// (async) Like `rm -rf`, removes everything recursively +const rmdir = async name => { + if ( ! await exists(name) ) { + return; + } + const paths = await traverse(name, true); + while (paths.length) { + const target = paths.pop(); + const dir = await isDir(target); + await new Promise( + (res, rej) => fs[dir ? 'rmdir' : 'unlink']( + target, + err => (err ? rej(err) : res()) + ) + ); + } +}; + + +// (async) Remove a file. Must be a file, not a folder. Just `fs.unlink`. +const rm = async name => { + if ( ! await exists(name) ) { + return; + } + await new Promise( + (res, rej) => fs.unlink(name, err => (err ? rej(err) : res())) + ); +}; + +module.exports = { + read, + write, + copy, + exists, + mkdir, + stat, + isDir, + isFile, + dirUp, + ensuredir, + copysafe, + readdir, + subdirs, + subfiles, + traverse, + copyall, + rmdir, + rm, +};