diff --git a/src/renderer/controllers/playback-controller.js b/src/renderer/controllers/playback-controller.js index c80489af..301354d4 100644 --- a/src/renderer/controllers/playback-controller.js +++ b/src/renderer/controllers/playback-controller.js @@ -188,7 +188,7 @@ module.exports = class PlaybackController { }, 10000) /* give it a few seconds */ if (torrentSummary.status === 'paused') { - dispatch('startTorrentingSummary', torrentSummary) + dispatch('startTorrentingSummary', torrentSummary.torrentKey) ipcRenderer.once('wt-ready-' + torrentSummary.infoHash, () => this.openPlayerFromActiveTorrent(torrentSummary, index, timeout, cb)) } else { diff --git a/src/renderer/controllers/torrent-list-controller.js b/src/renderer/controllers/torrent-list-controller.js index bb39ce97..647ddf15 100644 --- a/src/renderer/controllers/torrent-list-controller.js +++ b/src/renderer/controllers/torrent-list-controller.js @@ -76,21 +76,25 @@ module.exports = class TorrentListController { } // Starts downloading and/or seeding a given torrentSummary. - startTorrentingSummary (torrentSummary) { - var s = torrentSummary - - // Backward compatibility for config files save before we had torrentKey - if (!s.torrentKey) s.torrentKey = this.state.nextTorrentKey++ + startTorrentingSummary (torrentKey) { + var s = TorrentSummary.getByKey(this.state, torrentKey) + if (!s) throw new Error('Missing key: ' + torrentKey) // Use Downloads folder by default if (!s.path) s.path = this.state.saved.prefs.downloadPath - ipcRenderer.send('wt-start-torrenting', - s.torrentKey, - TorrentSummary.getTorrentID(s), - s.path, - s.fileModtimes, - s.selections) + fs.stat(TorrentSummary.getFileOrFolder(s), function (err) { + if (err) { + s.error = 'path-missing' + return + } + ipcRenderer.send('wt-start-torrenting', + s.torrentKey, + TorrentSummary.getTorrentID(s), + s.path, + s.fileModtimes, + s.selections) + }) } // TODO: use torrentKey, not infoHash @@ -98,7 +102,7 @@ module.exports = class TorrentListController { var torrentSummary = TorrentSummary.getByKey(this.state, infoHash) if (torrentSummary.status === 'paused') { torrentSummary.status = 'new' - this.startTorrentingSummary(torrentSummary) + this.startTorrentingSummary(torrentSummary.torrentKey) sound.play('ENABLE') } else { torrentSummary.status = 'paused' diff --git a/src/renderer/lib/state.js b/src/renderer/lib/state.js index 3205e1df..04350337 100644 --- a/src/renderer/lib/state.js +++ b/src/renderer/lib/state.js @@ -200,6 +200,9 @@ function save (state, cb) { if (key === 'playStatus') { continue // Don't save whether a torrent is playing / pending } + if (key === 'error') { + continue // Don't save error states + } torrent[key] = x[key] } return torrent diff --git a/src/renderer/main.js b/src/renderer/main.js index bfb1c4f0..15fd2183 100644 --- a/src/renderer/main.js +++ b/src/renderer/main.js @@ -75,15 +75,15 @@ function onState (err, _state) { } }) + // Restart everything we were torrenting last time the app ran + resumeTorrents() + // Calling update() updates the UI given the current state // Do this at least once a second to give every file in every torrentSummary // a progress bar and to keep the cursor in sync when playing a video setInterval(update, 1000) app = ReactDOM.render(, document.querySelector('#body')) - // Restart everything we were torrenting last time the app ran - resumeTorrents() - // Lazy-load other stuff, like the AppleTV module, later to keep startup fast window.setTimeout(delayedInit, config.DELAYED_INIT) @@ -179,8 +179,7 @@ const dispatchHandlers = { 'deleteTorrent': (infoHash, deleteData) => controllers.torrentList.deleteTorrent(infoHash, deleteData), 'toggleSelectTorrent': (infoHash) => controllers.torrentList.toggleSelectTorrent(infoHash), 'openTorrentContextMenu': (infoHash) => controllers.torrentList.openTorrentContextMenu(infoHash), - 'startTorrentingSummary': (torrentSummary) => - controllers.torrentList.startTorrentingSummary(torrentSummary), + 'startTorrentingSummary': (torrentKey) => controllers.torrentList.startTorrentingSummary(torrentKey), // Playback 'playFile': (infoHash, index) => controllers.playback.playFile(infoHash, index), @@ -318,8 +317,14 @@ function escapeBack () { // Starts all torrents that aren't paused on program startup function resumeTorrents () { state.saved.torrents - .filter((torrentSummary) => torrentSummary.status !== 'paused') - .forEach((torrentSummary) => controllers.torrentList.startTorrentingSummary(torrentSummary)) + .map((torrentSummary) => { + // Torrent keys are ephemeral, reassigned each time the app runs. + // On startup, give all torrents a key, even the ones that are paused. + torrentSummary.torrentKey = state.nextTorrentKey++ + return torrentSummary + }) + .filter((s) => s.status !== 'paused') + .forEach((s) => controllers.torrentList.startTorrentingSummary(s.torrentKey)) } // Set window dimensions to match video dimensions or fill the screen @@ -440,7 +445,6 @@ function onFullscreenChanged (e, isFullScreen) { } function checkDownloadPath () { - state.downloadPathStatus = undefined fs.stat(state.saved.prefs.downloadPath, function (err, stat) { if (err) { state.downloadPathStatus = 'missing' diff --git a/src/renderer/views/torrent-list.js b/src/renderer/views/torrent-list.js index a4849906..50cf8bfe 100644 --- a/src/renderer/views/torrent-list.js +++ b/src/renderer/views/torrent-list.js @@ -9,31 +9,27 @@ module.exports = class TorrentList extends React.Component { render () { var state = this.props.state - var contents - if (!state.downloadPathStatus) { - contents = '' - } else if (state.downloadPathStatus === 'missing') { - contents = ( -
+ var contents = [] + if (state.downloadPathStatus === 'missing') { + contents.push( +

Download path missing: {state.saved.prefs.downloadPath}

Check that all drives are connected?

-

Alternatively, choose a new download path in - Preferences +

Alternatively, choose a new download path + in Preferences

) - } else if (state.downloadPathStatus === 'ok') { - contents = state.saved.torrents.map( - (torrentSummary) => this.renderTorrent(torrentSummary) - ) - contents.push( -
- Drop a torrent file here or paste a magnet link -
- ) - } else { - throw new Error('Unhandled downloadPathStatus ' + state.downloadPathStatus) } + var torrentElems = state.saved.torrents.map( + (torrentSummary) => this.renderTorrent(torrentSummary) + ) + contents.push(...torrentElems) + contents.push( +
+ Drop a torrent file here or paste a magnet link +
+ ) return (
@@ -64,7 +60,7 @@ module.exports = class TorrentList extends React.Component { if (torrentSummary.playStatus) classes.push(torrentSummary.playStatus) if (isSelected) classes.push('selected') if (!infoHash) classes.push('disabled') - if (torrentSummary.torrrentKey) console.error('Missing torrentKey', torrentSummary) + if (!torrentSummary.torrentKey) throw new Error('Missing torrentKey') return (
+ {getErrorMessage(torrentSummary)} +
+ ) + } else if (torrentSummary.status !== 'paused' && prog) { + elements.push(
{renderPercentProgress()} {renderTotalProgress()} @@ -98,7 +100,7 @@ module.exports = class TorrentList extends React.Component { {renderUploadSpeed()} {renderEta()}
- )) + ) } return (
{elements}
) @@ -195,8 +197,9 @@ module.exports = class TorrentList extends React.Component { } // Only show the play button for torrents that contain playable media - var playButton - if (TorrentPlayer.isPlayableTorrentSummary(torrentSummary)) { + var playButton, downloadButton + var noErrors = !torrentSummary.error + if (noErrors && TorrentPlayer.isPlayableTorrentSummary(torrentSummary)) { playButton = ( ) } - - return ( -
- {positionElem} - {playButton} + if (noErrors) { + downloadButton = ( {downloadIcon} + ) + } + + return ( +
+ {positionElem} + {playButton} + {downloadButton} {message}
) + if (torrentSummary.error || !torrentSummary.files) { + var message = '' + if (torrentSummary.error === 'path-missing') { + // Special case error: this torrent's download dir or file is missing + message = 'Missing path: ' + TorrentSummary.getFileOrFolder(torrentSummary) + } else if (torrentSummary.error) { + // General error for this torrent: just show the message + message = torrentSummary.error.message || torrentSummary.error + } else if (torrentSummary.status === 'paused') { + // No file info, no infohash, and we're not trying to download from the DHT + message = 'Failed to load torrent info. Click the download button to try again...' + } else { + // No file info, no infohash, trying to load from the DHT + message = 'Downloading torrent info...' + } + filesElement = ( +
+ {message} +
+ ) } else { // We do know the files. List them and show download stats for each one var fileRows = torrentSummary.files @@ -349,3 +371,16 @@ module.exports = class TorrentList extends React.Component { ) } } + +function getErrorMessage (torrentSummary) { + var err = torrentSummary.error + if (err === 'path-missing') { + return ( + + Path missing.
+ Fix and restart the app, or delete the torrent. +
+ ) + } + return 'Error' +} diff --git a/static/main.css b/static/main.css index 902c4c4e..a2b1a8b7 100644 --- a/static/main.css +++ b/static/main.css @@ -554,8 +554,14 @@ input[type='text'] { /* * TORRENT LIST: ERRORS */ + .torrent-list p { - padding: 5px 20px; + margin: 10px 20px; +} + +.torrent-list a { + color: #99f; + text-decoration: none; } /*