mirror of
https://github.com/Myxelium/Bridge-Multi.git
synced 2026-04-11 14:19:38 +00:00
154 lines
5.0 KiB
TypeScript
154 lines
5.0 KiB
TypeScript
import { DownloadError } from './FileDownloader'
|
|
import { readdir as _readdir, unlink as _unlink, lstat as _lstat, copyFile as _copyFile,
|
|
rmdir as _rmdir, access as _access, mkdir as _mkdir, constants } from 'fs'
|
|
import { promisify } from 'util'
|
|
import { join, extname } from 'path'
|
|
import * as node7z from 'node-7z'
|
|
import * as zipBin from '7zip-bin'
|
|
import * as unrarjs from 'node-unrar-js'
|
|
import { getSettingsHandler } from '../SettingsHandler.ipc'
|
|
const getSettings = getSettingsHandler.getSettings
|
|
|
|
const readdir = promisify(_readdir)
|
|
const unlink = promisify(_unlink)
|
|
const lstat = promisify(_lstat)
|
|
const copyFile = promisify(_copyFile)
|
|
const rmdir = promisify(_rmdir)
|
|
const access = promisify(_access)
|
|
const mkdir = promisify(_mkdir)
|
|
|
|
type EventCallback = {
|
|
'extract': (filename: string) => void
|
|
'extractProgress': (percent: number, fileCount: number) => void
|
|
'transfer': (filepath: string) => void
|
|
'complete': (filepath: string) => void
|
|
'error': (error: DownloadError, retry: () => void) => void
|
|
}
|
|
type Callbacks = { [E in keyof EventCallback]: EventCallback[E] }
|
|
|
|
export class FileExtractor {
|
|
|
|
private callbacks = {} as Callbacks
|
|
private libraryFolder: string
|
|
private wasCanceled = false
|
|
constructor(private sourceFolder: string, private isArchive: boolean, private destinationFolderName: string) { }
|
|
|
|
/**
|
|
* Calls `callback` when `event` fires.
|
|
*/
|
|
on<E extends keyof EventCallback>(event: E, callback: EventCallback[E]) {
|
|
this.callbacks[event] = callback
|
|
}
|
|
|
|
/**
|
|
* Starts the chart extraction process.
|
|
*/
|
|
async beginExtract() {
|
|
this.libraryFolder = getSettings().libraryPath
|
|
const files = await readdir(this.sourceFolder)
|
|
if (this.isArchive) {
|
|
this.extract(files[0])
|
|
} else {
|
|
this.transfer()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Extracts the file at `filename` to `this.sourceFolder`.
|
|
*/
|
|
private extract(filename: string) {
|
|
if (this.wasCanceled) { return } // CANCEL POINT
|
|
this.callbacks.extract(filename)
|
|
const source = join(this.sourceFolder, filename)
|
|
|
|
if (extname(filename) == '.rar') {
|
|
|
|
// Use node-unrar-js to extract the archive
|
|
try {
|
|
let extractor = unrarjs.createExtractorFromFile(source, this.sourceFolder)
|
|
extractor.extractAll()
|
|
} catch (err) {
|
|
this.callbacks.error({ header: 'Extract Failed.', body: `Unable to extract [${filename}]: ${err.name}` }, () => this.extract(filename))
|
|
return
|
|
}
|
|
this.transfer(source)
|
|
|
|
} else {
|
|
|
|
// Use node-7z to extract the archive
|
|
const stream = node7z.extractFull(source, this.sourceFolder, { $progress: true, $bin: zipBin.path7za })
|
|
|
|
stream.on('progress', (progress: { percent: number, fileCount: number }) => {
|
|
this.callbacks.extractProgress(progress.percent, progress.fileCount)
|
|
})
|
|
|
|
stream.on('error', (err: Error) => {
|
|
this.callbacks.error({ header: 'Extract Failed.', body: `Unable to extract [${filename}]: ${err.name}` }, () => this.extract(filename))
|
|
})
|
|
|
|
stream.on('end', () => {
|
|
this.transfer(source)
|
|
})
|
|
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Deletes the archive at `archiveFilepath`, then transfers the extracted chart to `this.libraryFolder`.
|
|
*/
|
|
private async transfer(archiveFilepath?: string) {
|
|
if (this.wasCanceled) { return } // CANCEL POINT
|
|
try {
|
|
|
|
// Create destiniation folder if it doesn't exist
|
|
const destinationFolder = join(this.libraryFolder, this.destinationFolderName)
|
|
this.callbacks.transfer(destinationFolder)
|
|
try {
|
|
await access(destinationFolder, constants.F_OK)
|
|
} catch (e) {
|
|
await mkdir(destinationFolder)
|
|
}
|
|
|
|
// Delete archive
|
|
if (archiveFilepath != undefined) {
|
|
try {
|
|
await unlink(archiveFilepath)
|
|
} catch (e) {
|
|
if (e.code != 'ENOENT') {
|
|
throw new Error(`Could not delete the archive file at [${archiveFilepath}]`)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check if it extracted to a folder instead of a list of files
|
|
let sourceFolder = this.sourceFolder
|
|
let files = await readdir(sourceFolder)
|
|
const isFolderArchive = (files.length < 2 && !(await lstat(join(sourceFolder, files[0]))).isFile())
|
|
if (isFolderArchive) {
|
|
sourceFolder = join(sourceFolder, files[0])
|
|
files = await readdir(sourceFolder)
|
|
}
|
|
|
|
if (this.wasCanceled) { return } // CANCEL POINT
|
|
|
|
// Copy the files from the temporary directory to the destination
|
|
for (const file of files) {
|
|
await copyFile(join(sourceFolder, file), join(destinationFolder, file))
|
|
await unlink(join(sourceFolder, file))
|
|
}
|
|
|
|
// Delete the temporary folders
|
|
await rmdir(sourceFolder)
|
|
if (isFolderArchive) {
|
|
await rmdir(join(sourceFolder, '..'))
|
|
}
|
|
this.callbacks.complete(destinationFolder)
|
|
} catch (e) {
|
|
this.callbacks.error({ header: 'Transfer Failed', body: `Unable to transfer downloaded files to the library folder: ${e.name}` }, () => this.transfer(archiveFilepath))
|
|
}
|
|
}
|
|
|
|
cancelExtract() {
|
|
this.wasCanceled = true
|
|
}
|
|
} |