no1b4me's picture
Upload 5037 files
95f4e64 verified
import debugFactory from 'debug'
import EventEmitter from 'events'
const debug = debugFactory('webtorrent:file-iterator')
/**
* Async iterator of a torrent file
*
* @param {File} file
* @param {Object} opts
* @param {number} opts.start iterator slice of file, starting from this byte (inclusive)
* @param {number} opts.end iterator slice of file, ending with this byte (inclusive)
*/
export default class FileIterator extends EventEmitter {
constructor (file, { start, end }) {
super()
this._torrent = file._torrent
this._pieceLength = file._torrent.pieceLength
this._startPiece = (start + file.offset) / this._pieceLength | 0
this._endPiece = (end + file.offset) / this._pieceLength | 0
this._piece = this._startPiece
this._offset = (start + file.offset) - (this._startPiece * this._pieceLength)
this._missing = end - start + 1
this._criticalLength = Math.min((1024 * 1024 / this._pieceLength) | 0, 2)
this._torrent._select(this._startPiece, this._endPiece, 1, null, true)
this.destroyed = false
}
[Symbol.asyncIterator] () {
return this
}
next () {
return new Promise((resolve, reject) => {
if (this._missing === 0 || this.destroyed) {
resolve({ done: true })
return this.destroy()
}
const pump = (index, opts) => {
if (!this._torrent.bitfield.get(index)) {
const listener = i => {
if (i === index || this.destroyed) {
this._torrent.removeListener('verified', listener)
pump(index, opts)
}
}
this._torrent.on('verified', listener)
return this._torrent.critical(index, index + this._criticalLength)
}
if (this._torrent.destroyed) return reject(new Error('Torrent removed'))
this._torrent.store.get(index, opts, (err, buffer) => {
if (this.destroyed) return resolve({ done: true }) // prevent hanging
debug('read %s and yielding (length %s) (err %s)', index, buffer?.length, err?.message)
if (err) return reject(err)
// prevent re-wrapping outside of promise
resolve({ value: buffer, done: false })
})
}
const length = Math.min(this._missing, this._pieceLength - this._offset)
pump(this._piece++, { length, offset: this._offset })
this._missing -= length
this._offset = 0
})
}
async return () {
this.destroy()
return { done: true }
}
async throw (err) {
throw err
}
destroy (cb = () => {}, err) {
if (this.destroyed) return
this.destroyed = true
if (!this._torrent.destroyed) {
this._torrent._deselect(this._startPiece, this._endPiece, true)
}
this.emit('return')
cb(err)
}
}