| 421 | } |
| 422 | |
| 423 | class FileChunker { |
| 424 | |
| 425 | constructor(file, onChunk, onPartitionEnd) { |
| 426 | this._chunkSize = 64000; // 64 KB |
| 427 | this._maxPartitionSize = 1e6; // 1 MB |
| 428 | this._offset = 0; |
| 429 | this._partitionSize = 0; |
| 430 | this._file = file; |
| 431 | this._onChunk = onChunk; |
| 432 | this._onPartitionEnd = onPartitionEnd; |
| 433 | this._reader = new FileReader(); |
| 434 | this._reader.addEventListener('load', e => this._onChunkRead(e.target.result)); |
| 435 | } |
| 436 | |
| 437 | nextPartition() { |
| 438 | this._partitionSize = 0; |
| 439 | this._readChunk(); |
| 440 | } |
| 441 | |
| 442 | _readChunk() { |
| 443 | const chunk = this._file.slice(this._offset, this._offset + this._chunkSize); |
| 444 | this._reader.readAsArrayBuffer(chunk); |
| 445 | } |
| 446 | |
| 447 | _onChunkRead(chunk) { |
| 448 | this._offset += chunk.byteLength; |
| 449 | this._partitionSize += chunk.byteLength; |
| 450 | this._onChunk(chunk); |
| 451 | if (this.isFileEnd()) return; |
| 452 | if (this._isPartitionEnd()) { |
| 453 | this._onPartitionEnd(this._offset); |
| 454 | return; |
| 455 | } |
| 456 | this._readChunk(); |
| 457 | } |
| 458 | |
| 459 | repeatPartition() { |
| 460 | this._offset -= this._partitionSize; |
| 461 | this._nextPartition(); |
| 462 | } |
| 463 | |
| 464 | _isPartitionEnd() { |
| 465 | return this._partitionSize >= this._maxPartitionSize; |
| 466 | } |
| 467 | |
| 468 | isFileEnd() { |
| 469 | return this._offset >= this._file.size; |
| 470 | } |
| 471 | |
| 472 | get progress() { |
| 473 | return this._offset / this._file.size; |
| 474 | } |
| 475 | } |
| 476 | |
| 477 | class FileDigester { |
| 478 |
nothing calls this directly
no outgoing calls
no test coverage detected