UNPKG

@empathize/framework

Version:

Framework for Neutralino

265 lines (264 loc) 10.2 kB
import promisify from '../async/promisify.js'; import path from '../paths/path.js'; import { DebugThread } from '../meta/Debug.js'; class Stream { /** * @param archive path to archive * @param unpackDir directory to extract the files to */ constructor(archive, unpackDir = null) { this._id = -1; /** * The interval in ms between progress event calls */ this.progressInterval = 500; this.unpacked = 0; this.started = false; this.finished = false; this.throwedError = false; this.path = archive; this.unpackDir = unpackDir; this.started = true; const debugThread = new DebugThread('Archive/Stream', { message: { 'path': archive, 'unpack dir': unpackDir } }); if (this.onStart) this.onStart(); Archive.getInfo(archive).then((info) => { if (info === null) { this.throwedError = true; if (this.onError) this.onError(); } else { this.archive = info; let command = { tar: `tar -xvf "${path.addSlashes(archive)}"${unpackDir ? ` -C "${path.addSlashes(unpackDir)}"` : ''}`, zip: `unzip -o "${path.addSlashes(archive)}"${unpackDir ? ` -d "${path.addSlashes(unpackDir)}"` : ''}`, '7z': `7z x "${path.addSlashes(archive)}"${unpackDir ? ` -o"${path.addSlashes(unpackDir)}"` : ''} -aoa` }[this.archive.type]; if (unpackDir) command = `mkdir -p "${path.addSlashes(unpackDir)}" && ${command}`; let remainedFiles = this.archive.files; const baseDir = unpackDir ?? NL_CWD; Neutralino.os.execCommand(command, { background: true }).then((result) => { this._id = result.pid; }); debugThread.log(`Unpacking started with command: ${command}`); const updateProgress = async () => { let difference = 0; let pool = []; remainedFiles.forEach((file) => { if (file.path != '#unpacked#') { pool.push(() => { return new Promise((resolve) => { Neutralino.filesystem.getStats(`${baseDir}/${file.path}`) .then(() => { this.unpacked += file.size.uncompressed; difference += file.size.uncompressed; file.path = '#unpacked#'; resolve(); }) .catch(() => resolve()); }); }); } }); await promisify({ callbacks: pool, callAtOnce: true, interval: 200 }); remainedFiles = remainedFiles.filter((file) => file.path != '#unpacked#'); if (this.onProgress) this.onProgress(this.unpacked, this.archive.size.uncompressed, difference); if (this.unpacked >= this.archive.size.uncompressed) { this.finished = true; debugThread.log('Unpacking finished'); if (this.onFinish) this.onFinish(); } if (!this.finished) setTimeout(updateProgress, this.progressInterval); }; setTimeout(updateProgress, this.progressInterval); } }); } /** * ID of the archive unpacker process */ get id() { return this._id; } /** * Specify event that will be called when the extraction has started * * @param callback */ start(callback) { this.onStart = callback; if (this.started) callback(); } /** * Specify event that will be called every [this.progressInterval] ms while extracting the archive * * @param callback */ progress(callback) { this.onProgress = callback; } /** * Specify event that will be called after the archive has been extracted * * @param callback */ finish(callback) { this.onFinish = callback; if (this.finished) callback(); } /** * Specify event that will be called if archive can't be extracted * * @param callback */ error(callback) { this.onError = callback; if (this.throwedError) callback(); } /** * Close unpacking stream */ close(forced = false) { Neutralino.os.execCommand(`kill ${forced ? '-9' : '-15'} ${this._id}`); } } export default class Archive { /** * Get type of archive * * @param path path to archive * @returns supported archive type or null */ static getType(path) { if (path.substring(path.length - 4) == '.zip') return 'zip'; else if (path.substring(path.length - 7, path.length - 2) == '.tar.') return 'tar'; else if (path.substring(path.length - 3) == '.7z') return '7z'; else return null; } /** * Get archive info * * @param path path to archive * @returns null if the archive has unsupported type. Otherwise - archive info */ static getInfo(path) { const debugThread = new DebugThread('Archive.getInfo', `Getting info about archive: ${path}`); return new Promise(async (resolve) => { const archiveType = this.getType(path); if (archiveType === null) resolve(null); else { let archive = { type: archiveType, size: { compressed: null, uncompressed: null }, files: [] }; switch (archive.type) { case 'tar': const tarOutput = await Neutralino.os.execCommand(`tar -tvf "${path}"`); for (const match of tarOutput.stdOut.matchAll(/^[dwxr\-]+ [\w/]+[ ]+(\d+) [0-9\-]+ [0-9\:]+ (.+)/gm)) { const fileSize = parseInt(match[1]); archive.size.uncompressed += fileSize; archive.files.push({ path: match[2], size: { compressed: null, uncompressed: fileSize } }); } break; case 'zip': const zipOutput = await Neutralino.os.execCommand(`unzip -v "${path}"`); for (const match of zipOutput.stdOut.matchAll(/^(\d+) [a-zA-Z\:]+[ ]+(\d+)[ ]+[0-9\-]+% [0-9\-]+ [0-9\:]+ [a-f0-9]{8} (.+)/gm)) { const uncompressedSize = parseInt(match[1]), compressedSize = parseInt(match[2]); archive.size.compressed += compressedSize; archive.size.uncompressed += uncompressedSize; archive.files.push({ path: match[3], size: { compressed: compressedSize, uncompressed: uncompressedSize } }); } break; case '7z': const output = (await Neutralino.os.execCommand(`7z l "${path}"`)) .stdOut.split('-------------------').slice(1, -1).join('-------------------'); for (const match of output.matchAll(/^[\d]+-[\d]+-[\d]+ [\d]+:[\d]+:[\d]+[a-zA-Z\. ]+([\d ]+)[ ]+([\d ]+)[ ]+(.+)/gm)) { const fileSize = parseInt(match[1].trim()); archive.size.uncompressed += fileSize; archive.files.push({ path: match[3], size: { compressed: null, uncompressed: fileSize } }); } break; } debugThread.log({ message: { 'type': archive.type, 'compressed size': archive.size.compressed, 'uncompressed size': archive.size.uncompressed, 'files amount': archive.files.length } }); resolve(archive); } }); } /** * Extract Archive * * @param path path to archive * @param unpackDir directory to extract the files to */ static extract(path, unpackDir = null) { return new Promise((resolve) => { const stream = new Stream(path, unpackDir); this.streams.push(stream); resolve(stream); }); } /** * Close every open archive extracting stream */ static closeStreams(forced = false) { this.streams.forEach((stream) => { stream.close(forced); }); } } Archive.streams = []; ; export { Stream };