const fs = require('fs'); const path = require('path'); const os = require('os'); const axios = require('axios'); const exec = require('child_process').exec; const spawn = require('child_process').spawn; const tryParse = (j, def) => { try { return JSON.parse(j); } catch (e) { return def; } }; module.exports.detectRepertoryMounts = (directory, version) => { return new Promise((resolve, reject) => { const processOptions = { detached: true, shell: false, windowsHide: true, }; const command = path.join(directory, version, (os.platform() === 'win32') ? 'repertory.exe' : 'repertory'); const args = []; args.push('-status'); const process = new spawn(command, args, processOptions); let result = ''; process.on('error', (err) => { reject(err); }); process.stdout.on('data', (d)=> { result += d; }); process.on('exit', () => { resolve(tryParse(result, { Hyperspace: { Active: false, Location: '', PID: -1, }, Sia: { Active: false, Location: '', PID: -1, }, })); }); process.unref(); }); }; module.exports.downloadFile = (url, destination, progressCallback, completeCallback) => { try { if (fs.existsSync(destination)) { fs.unlinkSync(destination); } } catch (e) { completeCallback(false, e); return; } axios .get(url, { responseType: 'stream', }) .then((response) => { const stream = fs.createWriteStream(destination); const total = parseInt(response.headers['content-length'], 10); let downloaded = 0; response.data.on('data', (chunk) => { stream.write(Buffer.from(chunk)); downloaded += chunk.length; progressCallback((downloaded / total * 100.0).toFixed(2)); }); response.data.on('end', () => { stream.end(() => { completeCallback(true); }); }); response.data.on('error', (e) => { stream.end(() => { completeCallback(false, e); }); }); }) .catch((e)=> { completeCallback(false, e); }); }; module.exports.executeAndWait = command => { return new Promise((resolve, reject) => { const retryExecute = (count, lastError) => { if (++count <= 5) { exec(command, (error) => { if (error) { if (error.code === 1) { setTimeout(() => { retryExecute(count, error); }, 1000); } else { reject(error); } } else { resolve(); } }); } else { reject(lastError); } }; retryExecute(0); }); }; module.exports.executeAsync = (command) => { return new Promise((resolve, reject) => { const launchProcess = (count, timeout) => { const processOptions = { detached: true, shell: true, }; const process = new spawn(command, [], processOptions); const pid = process.pid; process.on('error', (err) => { if (++count === 5) { reject(err, pid); } else { clearTimeout(timeout); setTimeout(()=>launchProcess(count, setTimeout(() => resolve(), 3000)), 1000); } }); process.on('exit', (code) => { if (++count === 5) { reject(code, pid); } else { setTimeout(()=>launchProcess(count, setTimeout(() => resolve(), 3000)), 1000); } }); process.unref(); }; launchProcess(0, setTimeout(() => resolve(), 3000)); }); }; module.exports.executeMount = (directory, version, storageType, location, exitCallback) => { return new Promise((resolve) => { const processOptions = { detached: true, shell: true, }; const command = path.join(directory, version, (os.platform() === 'win32') ? 'repertory.exe' : 'repertory'); const args = []; if (storageType.toLowerCase() === 'hyperspace') { args.push('-hs'); } if (os.platform() === 'linux') { args.push("-o"); args.push("big_writes"); } if (os.platform() === 'win32') { args.push('-hidden'); } args.push(location); const process = new spawn(command, args, processOptions); const pid = process.pid; const timeout = setTimeout(() => { resolve(pid); }, 3000); process.on('error', (err) => { clearTimeout(timeout); exitCallback(err, pid); }); process.on('exit', (code) => { clearTimeout(timeout); exitCallback(code, pid); }); process.unref(); }); }; module.exports.getConfig = (directory, version, storageType) => { return new Promise((resolve, reject) => { const processOptions = { detached: true, shell: false, windowsHide: true, }; const command = path.join(directory, version, (os.platform() === 'win32') ? 'repertory.exe' : 'repertory'); const args = []; args.push('-dc'); if (storageType.toLowerCase() === 'hyperspace') { args.push('-hs'); } const process = new spawn(command, args, processOptions); let result = ''; process.on('error', (err) => { reject(err); }); process.stdout.on('data', (d)=> { result += d; }); process.on('exit', () => { const lines = result .replace(/\r\n/g, '\n') .split('\n'); const code = parseInt(lines[0], 10); if (code === 0) { lines.shift(); resolve({ Code: code, Data: JSON.parse(lines.join('\n')), }); } else { resolve(code); } }); process.unref(); }); }; module.exports.getConfigTemplate = (directory, version, storageType) => { return new Promise((resolve, reject) => { const processOptions = { detached: true, shell: false, windowsHide: true, }; const command = path.join(directory, version, (os.platform() === 'win32') ? 'repertory.exe' : 'repertory'); const args = []; args.push('-gt'); if (storageType.toLowerCase() === 'hyperspace') { args.push('-hs'); } const process = new spawn(command, args, processOptions); let result = ''; process.on('error', (err) => { reject(err); }); process.stdout.on('data', (d)=> { result += d; }); process.on('exit', () => { resolve(JSON.parse(result)); }); process.unref(); }); }; module.exports.getMissingDependencies = dependencies => { return new Promise((resolve, reject) => { if (!dependencies || (dependencies.length === 0)) { reject(Error('Dependency list is empty')); } let missing = []; if (os.platform() === 'win32') { let count = 0; const resolveIfComplete = () => { if (++count === dependencies.length) { resolve(missing); } }; const Registry = require('winreg'); for (const dep of dependencies) { let hive = null; const hiveName = dep.registry[0].split('\\')[0]; switch (hiveName) { case 'HKEY_CLASSES_ROOT': hive = Registry.HKCR; break; case 'HKEY_CURRENT_CONFIG': hive = Registry.HKCC; break; case 'HKEY_CURRENT_USER': hive = Registry.HKCU; break; case 'HKEY_LOCAL_MACHINE': hive = Registry.HKLM; break; case 'HKEY_USERS': hive = Registry.HKU; break; default: throw Error('Invalid registry hive: ' + hiveName); } const key = dep.registry[0].substr(hiveName.length); const regKey = new Registry({ hive: hive, key: key }); regKey.valueExists('DisplayName', (err, exists) => { if (err || !exists) { regKey.valueExists('ProductName', (err, exists) => { if (err || !exists) { missing.push(dep); } resolveIfComplete(); }); } else { resolveIfComplete(); } }); } } else { for (const dep of dependencies) { try { if (!fs.lstatSync(dep.file).isFile()) { missing.push(dep); } } catch (e) { missing.push(dep); } } resolve(missing); } }); }; //https://stackoverflow.com/questions/31645738/how-to-create-full-path-with-nodes-fs-mkdirsync module.exports.mkDirByPathSync = (targetDir, { isRelativeToScript = false } = {}) => { const sep = path.sep; const initDir = path.isAbsolute(targetDir) ? sep : ''; const baseDir = isRelativeToScript ? __dirname : '.'; return targetDir.split(sep).reduce((parentDir, childDir) => { const curDir = path.resolve(baseDir, parentDir, childDir); try { fs.mkdirSync(curDir); } catch (err) { if (err.code === 'EEXIST') { // curDir already exists! return curDir; } // To avoid `EISDIR` error on Mac and `EACCES`-->`ENOENT` and `EPERM` on Windows. if (err.code === 'ENOENT') { // Throw the original parentDir error on curDir `ENOENT` failure. throw new Error(`EACCES: permission denied, mkdir '${parentDir}'`); } const caughtErr = ['EACCES', 'EPERM', 'EISDIR'].indexOf(err.code) > -1; if (!caughtErr || (caughtErr && (targetDir === curDir))) { throw err; // Throw if it's just the last created dir. } } return curDir; }, initDir); }; module.exports.removeDirectoryRecursively = (p) => { if (fs.existsSync(p)) { fs .readdirSync(p) .forEach(file => { const curPath = path.join(p, file); if (fs.lstatSync(curPath).isDirectory()) { module.exports.removeDirectoryRecursively(curPath); } else { fs.unlinkSync(curPath); } }); fs.rmdirSync(p); } }; module.exports.resolvePath = str => { if (os.platform() === 'win32') { return str.replace(/%([^%]+)%/g, (_, n) => { return process.env[n]; }); } else { return str.replace('~', os.homedir()); } }; module.exports.setConfigValue = (name, value, directory, storageType, version) => { return new Promise((resolve, reject) => { const processOptions = { detached: true, shell: false, windowsHide: true, }; const command = path.join(directory, version, (os.platform() === 'win32') ? 'repertory.exe' : 'repertory'); const args = []; args.push('-set'); args.push(name); args.push(value); if (storageType.toLowerCase() === 'hyperspace') { args.push('-hs'); } const process = new spawn(command, args, processOptions); process.on('error', (err) => { reject(err); }); process.on('exit', () => { resolve(); }); process.unref(); }); }; module.exports.stopProcessByPID = pid => { return new Promise((resolve, reject) => { const processOptions = { detached: true, shell: false, windowsHide: true, }; const command = (os.platform() === 'win32') ? 'taskkill.exe' : ''; const args = []; args.push('/PID'); args.push(pid); const process = new spawn(command, args, processOptions); process.on('error', (err) => { reject(err); }); process.on('exit', () => { setTimeout(()=>resolve(pid), 3000); }); process.unref(); }); };