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;
}
/*