UNPKG

ledown

Version:

A library for nodejs to download http large file in pauseable and resumable way

132 lines (115 loc) 3.13 kB
/** @flow */ import EventEmitter from 'events' import { promisify } from 'util' import { write } from 'fs' import TaskMeta from './TaskMeta' import Task from './Task' import fetch from './fetch' import { type IncomingMessage } from 'http' const writeAsync = promisify(write) export type DownloadOptions = { windowSize: number, fd: number } export default class TaskThread extends EventEmitter { taskMeta: TaskMeta options: DownloadOptions currentPosition: number currentRequest: IncomingMessage isRunning: boolean = false pendingWrite: number = 0 constructor (taskMeta: TaskMeta, options: DownloadOptions) { super() this.taskMeta = taskMeta this.options = options this.handleData = this.handleData.bind(this) this.handleSingleRequestEnd = this.handleSingleRequestEnd.bind(this) } executeRequest = async () => { if (!this.isRunning) return const range = this.taskMeta.allocRange(this.options.windowSize) if (!range) { return this.emitEnd() } try { this.currentPosition = range[0] this.currentRequest = await fetch(this.taskMeta.url, { method: 'GET', headers: { Range: `bytes=${range[0]}-${range[1] - 1}` } }) if (this.currentRequest.statusCode >= 400) { throw new Error(Task.ERROR.ERR_NETWORK) } this.currentRequest.on('end', this.handleSingleRequestEnd) this.currentRequest.on('data', this.handleData) } catch (e) { this.emitError(e) } } start (): void { if (this.isRunning) return this.isRunning = true this.executeRequest() } async stop (): void { if (!this.isRunning) return this.isRunning = false if (this.currentRequest) { this.currentRequest.destroy() this.currentRequest = null } if (this.pendingWrite > 0) { await this.awaitPendingWrite() } } async awaitPendingWrite () { return new Promise(resolve => { let awaitProgress = () => { if (this.pendingWrite === 0) { this.off('progress', awaitProgress) resolve() } } if (this.pendingWrite === 0) { resolve() } else { this.on('progress', awaitProgress) } }) } async handleData (data: Buffer) { if (!this.isRunning) return const start = this.currentPosition const end = start + data.byteLength this.currentPosition = end this.pendingWrite++ try { await writeAsync(this.options.fd, data, 0, data.byteLength, start) } catch (e) { this.pendingWrite-- return this.emitError(new Error(Task.ERROR.ERR_DISK_IO_WRITE)) } this.pendingWrite-- this.taskMeta.completeRange([start, end]) this.emit('progress') } async emitError (e) { await this.stop() this.emit('error', e) } async emitEnd () { await this.stop() this.emit('end') } async handleSingleRequestEnd () { if (!this.isRunning) return this.currentRequest = null await this.awaitPendingWrite() this.executeRequest() } off (...arg) { return this.removeListener(...arg) } }