New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add time remaining estimate and minor fixes #56
Merged
+448
−73
Merged
Changes from 1 commit
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
24b6fd4
added time remaining estimate to console output; several small fixes
transitive-bullshit b784a88
merge changes
transitive-bullshit f98a3fd
removed extraneous comment
transitive-bullshit 1529dcf
stream to VLC is now working
transitive-bullshit da72f6b
adding FSStorage; webtorrent now can save the results of a torrent do…
transitive-bullshit d6d66ec
added small optimization to FSStorage.readBlock to read blocks from i…
transitive-bullshit 196d9ee
support peer blocklist
transitive-bullshit 976fd11
added missing commandline blocklist parsing for blocklist support
transitive-bullshit a1afa10
.
transitive-bullshit 8021381
fixed possibility of hanging process on --remove and fixed an error i…
transitive-bullshit File filter...
Filter file types
Jump to…
Jump to file or symbol
Failed to load files and symbols.
Loading status checks…
adding FSStorage; webtorrent now can save the results of a torrent do…
…wnload to the filesystem
- Loading branch information
| @@ -0,0 +1,190 @@ | ||
| module.exports = FSStorage | ||
|
|
||
| var Storage = require('bittorrent-client').Storage | ||
| var inherits = require('inherits') | ||
| var extend = require('extend.js') | ||
| var os = require('os') | ||
| var fs = require('fs') | ||
| var path = require('path') | ||
| var raf = require('random-access-file') | ||
| var mkdirp = require('mkdirp') | ||
| var rimraf = require('rimraf') | ||
| var thunky = require('thunky') | ||
|
|
||
| var TMP = fs.existsSync('/tmp') ? '/tmp' : os.tmpDir() | ||
|
|
||
| inherits(FSStorage, Storage) | ||
|
|
||
| /** | ||
| * fs-backed Storage for a torrent download. | ||
| * | ||
| * @param {Object} parsedTorrent | ||
| * @param {Object} opts | ||
| */ | ||
| function FSStorage (parsedTorrent, opts) { | ||
| var self = this | ||
| opts = extend({ | ||
| nobuffer: true, | ||
| tmp: TMP, | ||
| name: 'webtorrent' | ||
| }, opts || {}) | ||
| Storage.call(self, parsedTorrent, opts) | ||
|
|
||
| if (!opts.path) { | ||
| opts.path = path.join(opts.tmp, opts.name, parsedTorrent.infoHash) | ||
| } | ||
|
|
||
| self.path = opts.path | ||
|
|
||
| self.piecesMap = [] | ||
| self.files.forEach(function (file) { | ||
| var fileStart = file.offset | ||
| var fileEnd = fileStart + file.length | ||
|
|
||
| var firstPiece = file.pieces[0].index | ||
| var lastPiece = file.pieces[file.pieces.length - 1].index | ||
| var pieceLength = file.pieceLength | ||
|
|
||
| var open = thunky(function (cb) { | ||
| var filePath = path.join(self.path, file.path) | ||
| var fileDir = path.dirname(filePath) | ||
|
|
||
| mkdirp(fileDir, function (err) { | ||
| if (err) return cb(err) | ||
| if (self.closed) return cb(new Error('Storage closed')) | ||
|
|
||
| var fd = raf(filePath) | ||
| file.fd = fd | ||
| cb(null, fd) | ||
| }) | ||
| }) | ||
|
|
||
| file.pieces.forEach(function (piece) { | ||
| var index = piece.index | ||
|
|
||
| var pieceStart = index * pieceLength | ||
| var pieceEnd = pieceStart + piece.length | ||
|
|
||
| var from = (fileStart < pieceStart) ? 0 : fileStart - pieceStart | ||
|
||
| var to = (fileEnd > pieceEnd) ? pieceLength : fileEnd - pieceStart | ||
| var offset = (fileStart > pieceStart) ? 0 : pieceStart - fileStart | ||
|
|
||
| if (!self.piecesMap[index]) self.piecesMap[index] = [] | ||
|
|
||
| self.piecesMap[index].push({ | ||
| from: from, | ||
| to: to, | ||
| offset: offset, | ||
| open: open | ||
| }) | ||
| }) | ||
| }) | ||
| } | ||
|
|
||
| FSStorage.prototype.readBlock = function (index, offset, length, cb) { | ||
| var self = this | ||
| if (!cb) return console.error('FSStorage.readBlock requires a callback') | ||
|
|
||
| var piece = self.pieces[index] | ||
| if (!piece) return cb(new Error("invalid piece index " + index)) | ||
|
|
||
| var rangeFrom = offset | ||
| var rangeTo = rangeFrom + length | ||
|
|
||
| var targets = self.piecesMap[index].filter(function (target) { | ||
| return (target.to > rangeFrom && target.from < rangeTo) | ||
| }) | ||
|
|
||
| if (!targets.length) return cb(new Error("no file matching the requested range?")) | ||
|
|
||
| var buffers = [] | ||
| var end = targets.length | ||
| var i = 0 | ||
|
|
||
| var readFromNextFile = function(err, buffer) { | ||
| if (err) return cb(err) | ||
| if (buffer) buffers.push(buffer) | ||
| if (i >= end) return cb(null, Buffer.concat(buffers)) | ||
|
|
||
| var target = targets[i++] | ||
|
|
||
| var from = target.from | ||
| var to = target.to | ||
| var offset = target.offset | ||
|
|
||
| if(to > rangeTo) to = rangeTo | ||
| if(from < rangeFrom) { | ||
| offset += rangeFrom - from | ||
| from = rangeFrom | ||
| } | ||
|
|
||
| target.open(function(err, file) { | ||
| if (err) return cb(err) | ||
| file.read(offset, to - from, readFromNextFile) | ||
| }) | ||
| } | ||
|
|
||
| readFromNextFile() | ||
| } | ||
|
|
||
| // flush pieces to file once they're done and verified | ||
| FSStorage.prototype._onPieceDone = function (piece) { | ||
| var self = this | ||
| var targets = self.piecesMap[piece.index] | ||
| var end = targets.length | ||
| var i = 0 | ||
|
|
||
| var writeToNextFile = function(err) { | ||
| if (err) return self.emit('error', err) | ||
| if (i >= end) { | ||
| return Storage.prototype._onPieceDone.call(self, piece) | ||
| } | ||
|
|
||
| var target = targets[i++] | ||
| target.open(function(err, file) { | ||
| if (err) return self.emit('error', err) | ||
| file.write(target.offset, piece.buffer.slice(target.from, target.to), writeToNextFile) | ||
| }) | ||
| } | ||
|
|
||
| writeToNextFile() | ||
| } | ||
|
|
||
| /** | ||
| * Removes and cleans up any backing store for this storage. | ||
| */ | ||
| FSStorage.prototype.remove = function (cb) { | ||
| var self = this | ||
| if (!cb) cb = noop | ||
|
|
||
| self.close(function (err) { | ||
| if (err) return cb(err) | ||
| var root = self.files[0].path.split(path.sep)[0] | ||
| rimraf(path.join(self.path, root), cb) | ||
| }) | ||
| } | ||
|
|
||
| /** | ||
| * Closes the backing store for this storage. | ||
| */ | ||
| FSStorage.prototype.close = function (cb) { | ||
| var self = this | ||
| if (!cb) cb = noop | ||
| if (self.closed) return cb() | ||
|
|
||
| Storage.prototype.close.call(self, function (err) { | ||
| if (err) return cb(err) | ||
|
|
||
| var i = 0 | ||
| function loop (err) { | ||
| if (i >= self.files.length) return cb() | ||
| if (err) return cb(err) | ||
| var next = self.files[i++] | ||
| if (!next) return process.nextTick(loop) | ||
| next.fd.close(loop) | ||
| } | ||
|
|
||
| process.nextTick(loop) | ||
| }) | ||
| } | ||
|
|
||
ProTip!
Use n and p to navigate between commits in a pull request.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
already included as
cp