diff --git a/renderer/index.css b/renderer/index.css index f0fe4654..4ac0c001 100644 --- a/renderer/index.css +++ b/renderer/index.css @@ -545,6 +545,10 @@ body.drag .torrent-placeholder span { border-spacing: 0; } +.torrent-details tr { + height: 28px; +} + .torrent-details tr:hover, .torrent-details .open-folder:hover { background-color: rgba(200, 200, 200, 0.3); @@ -553,6 +557,7 @@ body.drag .torrent-placeholder span { .torrent-details td { overflow: hidden; padding: 0; + vertical-align: bottom; } .torrent-details td.col-icon { diff --git a/renderer/index.js b/renderer/index.js index 51081f85..4f43ac8d 100644 --- a/renderer/index.js +++ b/renderer/index.js @@ -63,6 +63,7 @@ function init () { } }) resumeTorrents() /* restart everything we were torrenting last time the app ran */ + setInterval(updateTorrentProgress, 1000) // The UI is built with virtual-dom, a minimalist library extracted from React // The concepts--one way data flow, a pure function that renders state to a @@ -421,6 +422,7 @@ function startTorrentingSummary (torrentSummary) { // Starts a given TorrentID, which can be an infohash, magnet URI, etc. Returns WebTorrent object // See https://github.com/feross/webtorrent/blob/master/docs/api.md#clientaddtorrentid-opts-function-ontorrent-torrent- function startTorrentingID (torrentID) { + console.log('Starting torrent ' + torrentID) var torrent = state.client.add(torrentID, { path: state.saved.downloadPath // Use downloads folder }) @@ -448,35 +450,67 @@ function addTorrentEvents (torrent) { torrent.on('done', torrentDone) function torrentReady () { + // Summarize torrent var torrentSummary = getTorrentSummary(torrent.infoHash) torrentSummary.status = 'downloading' torrentSummary.ready = true torrentSummary.name = torrentSummary.displayName || torrent.name torrentSummary.infoHash = torrent.infoHash + + // Summarize torrent files torrentSummary.files = torrent.files.map(summarizeFileInTorrent) + updateTorrentProgress() - saveTorrentFile(torrentSummary, torrent) + // Save the .torrent file, if it hasn't been saved already + if (!torrentSummary.torrentPath) saveTorrentFile(torrentSummary, torrent) - if (!torrentSummary.posterURL) { - generateTorrentPoster(torrent, torrentSummary) - } + // Auto-generate a poster image, if it hasn't been generated already + if (!torrentSummary.posterURL) generateTorrentPoster(torrent, torrentSummary) update() } function torrentDone () { + // UPdate the torrent summary var torrentSummary = getTorrentSummary(torrent.infoHash) torrentSummary.status = 'seeding' + updateTorrentProgress() + // Notify the user that a torrent finished if (!state.window.isFocused) { state.dock.badge += 1 } - showDoneNotification(torrent) + update() } } +function updateTorrentProgress () { + // TODO: ideally this would be tracked by WebTorrent, which could do it + // more efficiently than looping over torrent.bitfield + var changed = false + state.client.torrents.forEach(function (torrent) { + var torrentSummary = getTorrentSummary(torrent.infoHash) + torrent.files.forEach(function (file, index) { + var numPieces = file._endPiece - file._startPiece + 1 + var numPiecesPresent = 0 + for (var piece = file._startPiece; piece <= file._endPiece; piece++) { + if (torrent.bitfield.get(piece)) numPiecesPresent++ + } + + var fileSummary = torrentSummary.files[index] + if (fileSummary.numPiecesPresent !== numPiecesPresent || fileSummary.numPieces !== numPieces) { + fileSummary.numPieces = numPieces + fileSummary.numPiecesPresent = numPiecesPresent + changed = true + } + }) + }) + + if (changed) update() +} + function generateTorrentPoster (torrent, torrentSummary) { torrentPoster(torrent, function (err, buf) { if (err) return onWarning(err) @@ -499,7 +533,8 @@ function summarizeFileInTorrent (file) { return { name: file.name, length: file.length, - isDownloaded: false + numPiecesPresent: 0, + numPieces: null } } diff --git a/renderer/lib/cast.js b/renderer/lib/cast.js index 4a1384b9..4ce8abc6 100644 --- a/renderer/lib/cast.js +++ b/renderer/lib/cast.js @@ -33,8 +33,6 @@ function init (callback) { }) var browser = airplay.createBrowser() - var devices = browser.getDevices(true) - console.log('TODO GET DEVICES RET %o', devices) browser.on('deviceOn', function (player) { state.devices.airplay = player addAirplayEvents() diff --git a/renderer/state.js b/renderer/state.js index 200237c5..4288b725 100644 --- a/renderer/state.js +++ b/renderer/state.js @@ -63,21 +63,48 @@ module.exports = { torrents: [ { status: 'paused', - infoHash: 'f84b51f0d2c3455ab5dabb6643b4340234cd036e', + infoHash: '88594aaacbde40ef3e2510c47374ec0aa396c08e', displayName: 'Big Buck Bunny', - posterURL: path.join(__dirname, '..', 'static', 'bigBuckBunny.jpg') + posterURL: path.join(__dirname, '..', 'static', 'bigBuckBunny.jpg'), + torrentPath: path.join(__dirname, '..', 'static', 'bigBuckBunny.torrent'), + files: [ + { + 'name': 'bbb_sunflower_1080p_30fps_normal.mp4', + 'length': 276134947, + 'numPiecesPresent': 0, + 'numPieces': 527 + } + ] }, { status: 'paused', infoHash: '6a9759bffd5c0af65319979fb7832189f4f3c35d', displayName: 'Sintel', - posterURL: path.join(__dirname, '..', 'static', 'sintel.jpg') + posterURL: path.join(__dirname, '..', 'static', 'sintel.jpg'), + torrentPath: path.join(__dirname, '..', 'static', 'sintel.torrent'), + files: [ + { + 'name': 'sintel.mp4', + 'length': 129241752, + 'numPiecesPresent': 0, + 'numPieces': 987 + } + ] }, { status: 'paused', infoHash: '02767050e0be2fd4db9a2ad6c12416ac806ed6ed', displayName: 'Tears of Steel', - posterURL: path.join(__dirname, '..', 'static', 'tearsOfSteel.jpg') + posterURL: path.join(__dirname, '..', 'static', 'tearsOfSteel.jpg'), + torrentPath: path.join(__dirname, '..', 'static', 'tearsOfSteel.torrent'), + files: [ + { + 'name': 'tears_of_steel_1080p.webm', + 'length': 571346576, + 'numPiecesPresent': 0, + 'numPieces': 2180 + } + ] } ], downloadPath: path.join(os.homedir(), 'Downloads') diff --git a/renderer/views/torrent-list.js b/renderer/views/torrent-list.js index 9e8ae857..1e432e25 100644 --- a/renderer/views/torrent-list.js +++ b/renderer/views/torrent-list.js @@ -131,11 +131,8 @@ function TorrentList (state, dispatch) { // Show files, per-file download status and play buttons, and so on function renderTorrentDetails (torrent, torrentSummary) { - // If we're currently torrenting, show details directly from WebTorrent, including % downloaded - // Otherwise, show a summary of files in the torrent, if available - var files = (torrent && torrent.files) || torrentSummary.files var filesElement - if (!files) { + if (!torrentSummary.files) { // We don't know what files this torrent contains var message = torrent ? 'Downloading torrent data using magnet link...' @@ -143,7 +140,8 @@ function TorrentList (state, dispatch) { filesElement = hx`
${message}
` } else { // We do know the files. List them and show download stats for each one - var fileRows = files.map((file, index) => renderFileRow(torrent, torrentSummary, file, index)) + var fileRows = torrentSummary.files.map( + (file, index) => renderFileRow(torrent, torrentSummary, file, index)) filesElement = hx`
Files @@ -167,39 +165,27 @@ function TorrentList (state, dispatch) { } } - // Show a single file in the details view for a single torrent - // File can either be a WebTorrent file (if we're currently torrenting it) or an element of torrentSummary.files + // Show a single torrentSummary file in the details view for a single torrent function renderFileRow (torrent, torrentSummary, file, index) { // First, find out how much of the file we've downloaded - // (If we're not currently torrenting, just say whether we have the file or not) - if (file._startPiece) { - var numPieces = file._endPiece - file._startPiece + 1 - var numPiecesPresent = 0 - for (var piece = file._startPiece; piece <= file._endPiece; piece++) { - if (torrent.bitfield.get(piece)) numPiecesPresent++ - } - var progress = Math.round(100 * numPiecesPresent / numPieces) + '%' - var isDone = numPieces === numPiecesPresent - } else { - var isDone = file.isDownloaded - var progress = hx`${isDone ? 'check' : 'close'}` - } + var isDone = file.numPiecesPresent === file.numPieces + var progress = Math.round(100 * file.numPiecesPresent / (file.numPieces || 0)) + '%' // Second, render the file as a table row var icon - var iconClass = '' + var rowClass = '' if (state.playing.infoHash === torrentSummary.infoHash && state.playing.fileIndex === index) { icon = 'pause_arrow' /* playing? add option to pause */ } else if (TorrentPlayer.isPlayable(file)) { icon = 'play_arrow' /* playable? add option to play */ } else { icon = 'description' /* file icon, opens in OS default app */ - iconClass = isDone ? '' : 'disabled' + rowClass = isDone ? '' : 'disabled' } return hx` - + - ${icon} + ${icon} ${file.name} ${progress} diff --git a/static/bigBuckBunny.jpg b/static/bigBuckBunny.jpg index 2ba08715..072dc5e9 100644 Binary files a/static/bigBuckBunny.jpg and b/static/bigBuckBunny.jpg differ diff --git a/static/bigBuckBunny.torrent b/static/bigBuckBunny.torrent new file mode 100644 index 00000000..f1c92cd3 Binary files /dev/null and b/static/bigBuckBunny.torrent differ diff --git a/static/sintel.torrent b/static/sintel.torrent index d8f20d66..cb7ca6c6 100644 Binary files a/static/sintel.torrent and b/static/sintel.torrent differ diff --git a/static/tearsOfSteel.torrent b/static/tearsOfSteel.torrent new file mode 100644 index 00000000..ccfc9e0c Binary files /dev/null and b/static/tearsOfSteel.torrent differ