// import * as zipBin from '7zip-bin' // import { mkdir as _mkdir, readdir, unlink } from 'fs' // import * as node7z from 'node-7z' // import * as unrarjs from 'node-unrar-js' // TODO find better rar library that has async extraction // import { FailReason } from 'node-unrar-js/dist/js/extractor' // import { extname, join } from 'path' // import { promisify } from 'util' // import { devLog } from '../../shared/ElectronUtilFunctions' // import { AnyFunction } from '../../shared/UtilFunctions' // import { DownloadError } from './ChartDownload' // const mkdir = promisify(_mkdir) // interface EventCallback { // 'start': (filename: string) => void // 'extractProgress': (percent: number, fileCount: number) => void // 'error': (err: DownloadError, retry: () => void | Promise) => void // 'complete': () => void // } // type Callbacks = { [E in keyof EventCallback]: EventCallback[E] } // const extractErrors = { // readError: (err: NodeJS.ErrnoException) => ({ header: `Failed to read file (${err.code})`, body: `${err.name}: ${err.message}` }), // emptyError: () => ({ header: 'Failed to extract archive', body: 'File archive was downloaded but could not be found' }), // rarmkdirError: (err: NodeJS.ErrnoException, sourceFile: string) => { // return { header: `Extracting archive failed. (${err.code})`, body: `${err.name}: ${err.message} (${sourceFile})` } // }, // rarextractError: (result: { reason: FailReason; msg: string }, sourceFile: string) => { // return { header: `Extracting archive failed: ${result.reason}`, body: `${result.msg} (${sourceFile})` } // }, // } // export class FileExtractor { // private callbacks = {} as Callbacks // private wasCanceled = false // constructor(private sourceFolder: string) { } // /** // * Calls `callback` when `event` fires. (no events will be fired after `this.cancelExtract()` is called) // */ // on(event: E, callback: EventCallback[E]) { // this.callbacks[event] = callback // } // /** // * Extract the chart from `this.sourceFolder`. (assumes there is exactly one archive file in that folder) // */ // beginExtract() { // setTimeout(this.cancelable(() => { // readdir(this.sourceFolder, (err, files) => { // if (err) { // this.callbacks.error(extractErrors.readError(err), () => this.beginExtract()) // } else if (files.length == 0) { // this.callbacks.error(extractErrors.emptyError(), () => this.beginExtract()) // } else { // this.callbacks.start(files[0]) // this.extract(join(this.sourceFolder, files[0]), extname(files[0]) == '.rar') // } // }) // }), 100) // Wait for filesystem to process downloaded file // } // /** // * Extracts the file at `fullPath` to `this.sourceFolder`. // */ // private async extract(fullPath: string, useRarExtractor: boolean) { // if (useRarExtractor) { // await this.extractRar(fullPath) // Use node-unrar-js to extract the archive // } else { // this.extract7z(fullPath) // Use node-7z to extract the archive // } // } // /** // * Extracts a .rar archive found at `fullPath` and puts the extracted results in `this.sourceFolder`. // * @throws an `ExtractError` if this fails. // */ // private async extractRar(fullPath: string) { // const extractor = unrarjs.createExtractorFromFile(fullPath, this.sourceFolder) // const fileList = extractor.getFileList() // if (fileList[0].state != 'FAIL') { // // Create directories for nested archives (because unrarjs didn't feel like handling that automatically) // const headers = fileList[1].fileHeaders // for (const header of headers) { // if (header.flags.directory) { // try { // await mkdir(join(this.sourceFolder, header.name), { recursive: true }) // } catch (err) { // this.callbacks.error( // extractErrors.rarmkdirError(err, fullPath), // () => this.extract(fullPath, extname(fullPath) == '.rar'), // ) // return // } // } // } // } // const extractResult = extractor.extractAll() // if (extractResult[0].state == 'FAIL') { // this.callbacks.error( // extractErrors.rarextractError(extractResult[0], fullPath), // () => this.extract(fullPath, extname(fullPath) == '.rar'), // ) // } else { // this.deleteArchive(fullPath) // } // } // /** // * Extracts a .zip or .7z archive found at `fullPath` and puts the extracted results in `this.sourceFolder`. // */ // private extract7z(fullPath: string) { // const zipBinPath = zipBin.path7za.replace('app.asar', 'app.asar.unpacked') // I love electron-builder packaging :) // const stream = node7z.extractFull(fullPath, this.sourceFolder, { $progress: true, $bin: zipBinPath }) // stream.on('progress', this.cancelable((progress: { percent: number; fileCount: number }) => { // this.callbacks.extractProgress(progress.percent, isNaN(progress.fileCount) ? 0 : progress.fileCount) // })) // let extractErrorOccured = false // stream.on('error', this.cancelable(() => { // extractErrorOccured = true // devLog(`Failed to extract [${fullPath}]; retrying with .rar extractor...`) // this.extract(fullPath, true) // })) // stream.on('end', this.cancelable(() => { // if (!extractErrorOccured) { // this.deleteArchive(fullPath) // } // })) // } // /** // * Tries to delete the archive at `fullPath`. // */ // private deleteArchive(fullPath: string) { // unlink(fullPath, this.cancelable(err => { // if (err && err.code != 'ENOENT') { // devLog(`Warning: failed to delete archive at [${fullPath}]`) // } // this.callbacks.complete() // })) // } // /** // * Stop the process of extracting the file. (no more events will be fired after this is called) // */ // cancelExtract() { // this.wasCanceled = true // } // /** // * Wraps a function that is able to be prevented if `this.cancelExtract()` was called. // */ // private cancelable(fn: F) { // return (...args: Parameters): ReturnType => { // if (this.wasCanceled) { return } // return fn(...Array.from(args)) // } // } // }