ledown
Version:
A library for nodejs to download http large file in pauseable and resumable way
150 lines (130 loc) • 3.71 kB
JavaScript
export type ByteRange = [number, number]
const cloneByteRange = data => {
if (Array.isArray(data)) {
return data.map(cloneByteRange)
}
return data
}
export default class ByteSheet {
/**
* @property indicates the range of bytes which remain to be download
*/
remaining: ByteRange[] = []
/** z
* @property indicates the range of bytes which are being downloading
*/
downloading: ByteRange[] = []
/**
* @property indicates the range of bytes which are finished downloading
*/
completed: ByteRange[] = []
constructor (arg: number | Array<ByteRange[]> = 0) {
if (typeof arg === 'number') {
this.remaining.push([0, arg])
} else {
const clone = cloneByteRange(arg)
this.remaining = clone[0]
this.downloading = []
this.completed = clone[1]
}
}
get completedByteCount (): number {
return this.getByteCountOf(this.completed)
}
get downloadingByteCount (): number {
return this.getByteCountOf(this.downloading)
}
get remainingByteCount (): number {
return this.getByteCountOf(this.remaining)
}
getByteCountOf (section: ByteRange[]): number {
return section.reduce((bytes: number, item: ByteRange) => {
return bytes + item[1] - item[0]
}, 0)
}
toJSONObject (): Array<ByteRange[]> {
const remaining = cloneByteRange(this.remaining)
const downloading = cloneByteRange(this.downloading)
const completed = cloneByteRange(this.completed)
return [
this.resolveRange([
...remaining,
...downloading
]),
completed
]
}
/**
* resolve an array of byte range
*/
resolveRange (ranges: ByteRange[]): ByteRange[] {
return ranges
.filter(range => range.length > 0 && range[1] > range[0])
.sort((prev, next) => prev[0] - next[0])
.reduce((sum: ByteRange[], range) => {
const last = sum[sum.length - 1]
if (last && last[1] >= range[0]) {
last[1] = last[1] > range[1] ? last[1] : range[1]
} else {
sum.push(range)
}
return sum
}, [])
}
/**
* take range of size from remaining byte range
* @param {number} size
*/
allocRangeToDownload (size: number): ByteRange {
const firstRange = this.remaining[0]
if (!firstRange) return null
const start = firstRange[0]
const end = Math.min(start + size, firstRange[1])
const range = [start, end]
this.remaining = this.cutSection(range, this.remaining)
this.downloading = this.resolveRange([
...this.downloading,
range
])
return range
}
setRangeAsCompleted (range: ByteRange): void {
this.downloading = this.cutSection(range, this.downloading)
this.completed = this.resolveRange([
...this.completed,
range
])
}
cutSection (cut: ByteRange, section: ByteRange[]): ByteRange[] {
return this.resolveRange(section.reduce((sec: ByteRange[], item) => {
let range = [...item]
sec.push(range)
// Range: |--------|
// Cut: |---|
if (cut[0] > range[0] && cut[1] < range[1]) {
sec.push([cut[1], range[1]])
range[1] = cut[0]
}
// Range: |--------|
// Cut: |----|
// Cut: |------------|
if (cut[0] <= range[0] && cut[1] > range[0]) {
range[0] = cut[1]
}
// Range: |--------|
// Cut: |----|
// Cut: |-------------|
if (cut[1] >= range[1] && cut[0] < range[1]) {
range[1] = cut[0]
}
return sec
}, []))
}
resetDownloading (): void {
this.remaining = this.resolveRange([
...this.remaining,
...this.downloading
])
this.downloading = []
}
}