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`