Spaces:
Running
Running
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) | |
} | |
} | |