Spaces:
Running
Running
File size: 5,041 Bytes
95f4e64 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 |
import EventEmitter from 'events'
import { Readable } from 'streamx'
import { chunkStoreRead } from 'chunk-store-iterator'
import mime from 'mime/lite.js'
import FileIterator from './file-iterator.js'
export default class File extends EventEmitter {
constructor (torrent, file) {
super()
this._torrent = torrent
this._destroyed = false
this._fileStreams = new Set()
this._iterators = new Set()
this.name = file.name
this.path = file.path
this.length = file.length
this.size = file.length
this.type = mime.getType(this.name) || 'application/octet-stream'
this.offset = file.offset
this.done = false
const start = file.offset
const end = start + file.length - 1
this._startPiece = start / this._torrent.pieceLength | 0
this._endPiece = end / this._torrent.pieceLength | 0
if (this.length === 0) {
this.done = true
this.emit('done')
}
this._client = torrent.client
}
get downloaded () {
if (this._destroyed || !this._torrent.bitfield) return 0
const { pieces, bitfield, pieceLength, lastPieceLength } = this._torrent
const { _startPiece: start, _endPiece: end } = this
const getPieceLength = (pieceIndex) => (
pieceIndex === pieces.length - 1 ? lastPieceLength : pieceLength
)
const getPieceDownloaded = (pieceIndex) => {
const len = pieceIndex === pieces.length - 1 ? lastPieceLength : pieceLength
if (bitfield.get(pieceIndex)) {
// verified data
return len
} else {
// "in progress" data
return len - pieces[pieceIndex].missing
}
}
let downloaded = 0
for (let index = start; index <= end; index += 1) {
const pieceDownloaded = getPieceDownloaded(index)
downloaded += pieceDownloaded
if (index === start) {
// First piece may have an offset, e.g. irrelevant bytes from the end of
// the previous file
const irrelevantFirstPieceBytes = this.offset % pieceLength
downloaded -= Math.min(irrelevantFirstPieceBytes, pieceDownloaded)
}
if (index === end) {
// Last piece may have an offset, e.g. irrelevant bytes from the start
// of the next file
const irrelevantLastPieceBytes = getPieceLength(end) - (this.offset + this.length) % pieceLength
downloaded -= Math.min(irrelevantLastPieceBytes, pieceDownloaded)
}
}
return downloaded
}
get progress () {
return this.length ? this.downloaded / this.length : 0
}
select (priority) {
if (this.length === 0) return
this._torrent.select(this._startPiece, this._endPiece, priority)
}
deselect () {
if (this.length === 0) return
this._torrent.deselect(this._startPiece, this._endPiece)
}
[Symbol.asyncIterator] (opts = {}) {
if (this.length === 0) return (async function * empty () {})()
const { start = 0 } = opts ?? {}
const end = (opts?.end && opts.end < this.length)
? opts.end
: this.length - 1
if (this.done) {
return chunkStoreRead(this._torrent.store, { offset: start + this.offset, length: end - start + 1 })
}
const iterator = new FileIterator(this, { start, end })
this._iterators.add(iterator)
iterator.once('return', () => {
this._iterators.delete(iterator)
})
return iterator
}
createReadStream (opts) {
const iterator = this[Symbol.asyncIterator](opts)
const fileStream = Readable.from(iterator)
this._fileStreams.add(fileStream)
fileStream.once('close', () => {
this._fileStreams.delete(fileStream)
})
return fileStream
}
async arrayBuffer (opts) {
const data = new Uint8Array(this.length)
let offset = 0
for await (const chunk of this[Symbol.asyncIterator](opts)) {
data.set(chunk, offset)
offset += chunk.length
}
return data.buffer
}
async blob (opts) {
return new Blob([await this.arrayBuffer(opts)], { type: this.type })
}
stream (opts) {
let iterator
return new ReadableStream({
start: () => {
iterator = this[Symbol.asyncIterator](opts)
},
async pull (controller) {
const { value, done } = await iterator.next()
if (done) {
controller.close()
} else {
controller.enqueue(value)
}
},
cancel () {
iterator.return()
}
})
}
get streamURL () {
if (!this._client._server) throw new Error('No server created')
return `${this._client._server.pathname}/${this._torrent.infoHash}/${this.path}`
}
streamTo (elem) {
elem.src = this.streamURL
return elem
}
includes (piece) {
return this._startPiece <= piece && this._endPiece >= piece
}
_destroy () {
this._destroyed = true
this._torrent = null
for (const fileStream of this._fileStreams) {
fileStream.destroy()
}
this._fileStreams.clear()
for (const iterator of this._iterators) {
iterator.destroy()
}
this._iterators.clear()
}
}
|