Merge remote-tracking branch 'feross/master'
This commit is contained in:
@@ -40,7 +40,7 @@ module.exports = class PlaybackController {
|
||||
|
||||
// Show a file in the OS, eg in Finder on a Mac
|
||||
openItem (infoHash, index) {
|
||||
var torrentSummary = torrentSummary.getByKey(this.state, infoHash)
|
||||
var torrentSummary = TorrentSummary.getByKey(this.state, infoHash)
|
||||
var filePath = path.join(
|
||||
torrentSummary.path,
|
||||
torrentSummary.files[index].path)
|
||||
|
||||
192
renderer/controllers/torrent-controller.js
Normal file
192
renderer/controllers/torrent-controller.js
Normal file
@@ -0,0 +1,192 @@
|
||||
const path = require('path')
|
||||
const ipcRenderer = require('electron').ipcRenderer
|
||||
|
||||
const TorrentSummary = require('../lib/torrent-summary')
|
||||
const TorrentPlayer = require('../lib/torrent-player')
|
||||
const sound = require('../lib/sound')
|
||||
const {dispatch} = require('../lib/dispatcher')
|
||||
|
||||
module.exports = class TorrentController {
|
||||
constructor (state) {
|
||||
this.state = state
|
||||
}
|
||||
|
||||
torrentInfoHash (torrentKey, infoHash) {
|
||||
var torrentSummary = this.getTorrentSummary(torrentKey)
|
||||
console.log('got infohash for %s torrent %s',
|
||||
torrentSummary ? 'existing' : 'new', torrentKey)
|
||||
|
||||
if (!torrentSummary) {
|
||||
var torrents = this.state.saved.torrents
|
||||
|
||||
// Check if an existing (non-active) torrent has the same info hash
|
||||
if (torrents.find((t) => t.infoHash === infoHash)) {
|
||||
ipcRenderer.send('wt-stop-torrenting', infoHash)
|
||||
return dispatch('error', 'Cannot add duplicate torrent')
|
||||
}
|
||||
|
||||
torrentSummary = {
|
||||
torrentKey: torrentKey,
|
||||
status: 'new'
|
||||
}
|
||||
torrents.unshift(torrentSummary)
|
||||
sound.play('ADD')
|
||||
}
|
||||
|
||||
torrentSummary.infoHash = infoHash
|
||||
dispatch('update')
|
||||
}
|
||||
|
||||
torrentWarning (torrentKey, message) {
|
||||
console.log('warning for torrent %s: %s', torrentKey, message)
|
||||
}
|
||||
|
||||
torrentError (torrentKey, message) {
|
||||
// TODO: WebTorrent needs semantic errors
|
||||
if (message.startsWith('Cannot add duplicate torrent')) {
|
||||
// Remove infohash from the message
|
||||
message = 'Cannot add duplicate torrent'
|
||||
}
|
||||
dispatch('error', message)
|
||||
|
||||
var torrentSummary = this.getTorrentSummary(torrentKey)
|
||||
if (torrentSummary) {
|
||||
console.log('Pausing torrent %s due to error: %s', torrentSummary.infoHash, message)
|
||||
torrentSummary.status = 'paused'
|
||||
dispatch('update')
|
||||
}
|
||||
}
|
||||
|
||||
torrentMetadata (torrentKey, torrentInfo) {
|
||||
// Summarize torrent
|
||||
var torrentSummary = this.getTorrentSummary(torrentKey)
|
||||
torrentSummary.status = 'downloading'
|
||||
torrentSummary.name = torrentSummary.displayName || torrentInfo.name
|
||||
torrentSummary.path = torrentInfo.path
|
||||
torrentSummary.magnetURI = torrentInfo.magnetURI
|
||||
// TODO: make torrentInfo immutable, save separately as torrentSummary.info
|
||||
// For now, check whether torrentSummary.files has already been set:
|
||||
var hasDetailedFileInfo = torrentSummary.files && torrentSummary.files[0].path
|
||||
if (!hasDetailedFileInfo) {
|
||||
torrentSummary.files = torrentInfo.files
|
||||
}
|
||||
if (!torrentSummary.selections) {
|
||||
torrentSummary.selections = torrentSummary.files.map((x) => true)
|
||||
}
|
||||
torrentSummary.defaultPlayFileIndex = TorrentPlayer.pickFileToPlay(torrentInfo.files)
|
||||
dispatch('update')
|
||||
|
||||
// Save the .torrent file, if it hasn't been saved already
|
||||
if (!torrentSummary.torrentFileName) ipcRenderer.send('wt-save-torrent-file', torrentKey)
|
||||
|
||||
// Auto-generate a poster image, if it hasn't been generated already
|
||||
if (!torrentSummary.posterFileName) ipcRenderer.send('wt-generate-torrent-poster', torrentKey)
|
||||
}
|
||||
|
||||
torrentDone (torrentKey, torrentInfo) {
|
||||
// Update the torrent summary
|
||||
var torrentSummary = this.getTorrentSummary(torrentKey)
|
||||
torrentSummary.status = 'seeding'
|
||||
|
||||
// Notify the user that a torrent finished, but only if we actually DL'd at least part of it.
|
||||
// Don't notify if we merely finished verifying data files that were already on disk.
|
||||
if (torrentInfo.bytesReceived > 0) {
|
||||
if (!this.state.window.isFocused) {
|
||||
this.state.dock.badge += 1
|
||||
}
|
||||
showDoneNotification(torrentSummary)
|
||||
ipcRenderer.send('downloadFinished', getTorrentPath(torrentSummary))
|
||||
}
|
||||
|
||||
dispatch('update')
|
||||
}
|
||||
|
||||
torrentProgress (progressInfo) {
|
||||
// Overall progress across all active torrents, 0 to 1
|
||||
var progress = progressInfo.progress
|
||||
var hasActiveTorrents = progressInfo.hasActiveTorrents
|
||||
|
||||
// Hide progress bar when client has no torrents, or progress is 100%
|
||||
// TODO: isn't this equivalent to: if (progress === 1) ?
|
||||
if (!hasActiveTorrents || progress === 1) {
|
||||
progress = -1
|
||||
}
|
||||
|
||||
// Show progress bar under the WebTorrent taskbar icon, on OSX
|
||||
this.state.dock.progress = progress
|
||||
|
||||
// Update progress for each individual torrent
|
||||
progressInfo.torrents.forEach((p) => {
|
||||
var torrentSummary = this.getTorrentSummary(p.torrentKey)
|
||||
if (!torrentSummary) {
|
||||
console.log('warning: got progress for missing torrent %s', p.torrentKey)
|
||||
return
|
||||
}
|
||||
torrentSummary.progress = p
|
||||
})
|
||||
|
||||
// TODO: Find an efficient way to re-enable this line, which allows subtitle
|
||||
// files which are completed after a video starts to play to be added
|
||||
// dynamically to the list of subtitles.
|
||||
// checkForSubtitles()
|
||||
|
||||
dispatch('update')
|
||||
}
|
||||
|
||||
torrentFileModtimes (torrentKey, fileModtimes) {
|
||||
var torrentSummary = this.getTorrentSummary(torrentKey)
|
||||
torrentSummary.fileModtimes = fileModtimes
|
||||
dispatch('saveStateThrottled')
|
||||
}
|
||||
|
||||
torrentFileSaved (torrentKey, torrentFileName) {
|
||||
console.log('torrent file saved %s: %s', torrentKey, torrentFileName)
|
||||
var torrentSummary = this.getTorrentSummary(torrentKey)
|
||||
torrentSummary.torrentFileName = torrentFileName
|
||||
dispatch('saveStateThrottled')
|
||||
}
|
||||
|
||||
torrentPosterSaved (torrentKey, posterFileName) {
|
||||
var torrentSummary = this.getTorrentSummary(torrentKey)
|
||||
torrentSummary.posterFileName = posterFileName
|
||||
dispatch('saveStateThrottled')
|
||||
}
|
||||
|
||||
torrentAudioMetadata (infoHash, index, info) {
|
||||
var torrentSummary = this.getTorrentSummary(infoHash)
|
||||
var fileSummary = torrentSummary.files[index]
|
||||
fileSummary.audioInfo = info
|
||||
dispatch('update')
|
||||
}
|
||||
|
||||
torrentServerRunning (serverInfo) {
|
||||
this.state.server = serverInfo
|
||||
}
|
||||
|
||||
// Gets a torrent summary {name, infoHash, status} from state.saved.torrents
|
||||
// Returns undefined if we don't know that infoHash
|
||||
getTorrentSummary (torrentKey) {
|
||||
return TorrentSummary.getByKey(this.state, torrentKey)
|
||||
}
|
||||
}
|
||||
|
||||
function getTorrentPath (torrentSummary) {
|
||||
var itemPath = TorrentSummary.getFileOrFolder(torrentSummary)
|
||||
if (torrentSummary.files.length > 1) {
|
||||
itemPath = path.dirname(itemPath)
|
||||
}
|
||||
return itemPath
|
||||
}
|
||||
|
||||
function showDoneNotification (torrent) {
|
||||
var notif = new window.Notification('Download Complete', {
|
||||
body: torrent.name,
|
||||
silent: true
|
||||
})
|
||||
|
||||
notif.onclick = function () {
|
||||
ipcRenderer.send('show')
|
||||
}
|
||||
|
||||
sound.play('DONE')
|
||||
}
|
||||
@@ -112,6 +112,14 @@ module.exports = class TorrentListController {
|
||||
ipcRenderer.send('wt-select-files', infoHash, torrentSummary.selections)
|
||||
}
|
||||
|
||||
confirmDeleteTorrent (infoHash, deleteData) {
|
||||
this.state.modal = {
|
||||
id: 'remove-torrent-modal',
|
||||
infoHash,
|
||||
deleteData
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: use torrentKey, not infoHash
|
||||
deleteTorrent (infoHash, deleteData) {
|
||||
ipcRenderer.send('wt-stop-torrenting', infoHash)
|
||||
@@ -151,14 +159,12 @@ module.exports = class TorrentListController {
|
||||
|
||||
menu.append(new electron.remote.MenuItem({
|
||||
label: 'Remove From List',
|
||||
click: () => this.deleteTorrent(
|
||||
torrentSummary.infoHash, false)
|
||||
click: () => dispatch('confirmDeleteTorrent', torrentSummary.infoHash, false)
|
||||
}))
|
||||
|
||||
menu.append(new electron.remote.MenuItem({
|
||||
label: 'Remove Data File',
|
||||
click: () => this.deleteTorrent(
|
||||
torrentSummary.infoHash, true)
|
||||
click: () => dispatch('confirmDeleteTorrent', torrentSummary.infoHash, true)
|
||||
}))
|
||||
|
||||
menu.append(new electron.remote.MenuItem({
|
||||
@@ -239,27 +245,23 @@ function findFilesRecursive (paths, cb) {
|
||||
function deleteFile (path) {
|
||||
if (!path) return
|
||||
fs.unlink(path, function (err) {
|
||||
if (err) dispatch('onError', err)
|
||||
if (err) dispatch('error', err)
|
||||
})
|
||||
}
|
||||
|
||||
// Delete all files in a torren
|
||||
// Delete all files in a torrent
|
||||
function moveItemToTrash (torrentSummary) {
|
||||
// TODO: delete directories, not just files
|
||||
torrentSummary.files.forEach(function (file) {
|
||||
var filePath = path.join(torrentSummary.path, file.path)
|
||||
console.log('DEBUG DELETING ' + filePath)
|
||||
ipcRenderer.send('moveItemToTrash', filePath)
|
||||
})
|
||||
var filePath = TorrentSummary.getFileOrFolder(torrentSummary)
|
||||
ipcRenderer.send('moveItemToTrash', filePath)
|
||||
}
|
||||
|
||||
function showItemInFolder (torrentSummary) {
|
||||
ipcRenderer.send('showItemInFolder', TorrentSummary.getTorrentPath(torrentSummary))
|
||||
ipcRenderer.send('showItemInFolder', TorrentSummary.getFileOrFolder(torrentSummary))
|
||||
}
|
||||
|
||||
function saveTorrentFileAs (torrentSummary) {
|
||||
var downloadPath = this.state.saved.prefs.downloadPath
|
||||
var newFileName = `${path.parse(torrentSummary.name).name}.torrent`
|
||||
var newFileName = path.parse(torrentSummary.name).name + '.torrent'
|
||||
var opts = {
|
||||
title: 'Save Torrent File',
|
||||
defaultPath: path.join(downloadPath, newFileName),
|
||||
|
||||
@@ -2,21 +2,21 @@ module.exports = {
|
||||
isPlayable,
|
||||
isVideo,
|
||||
isAudio,
|
||||
isPlayableTorrent,
|
||||
isTorrent,
|
||||
isPlayableTorrentSummary,
|
||||
pickFileToPlay
|
||||
}
|
||||
|
||||
var path = require('path')
|
||||
|
||||
/**
|
||||
* Determines whether a file in a torrent is audio/video we can play
|
||||
*/
|
||||
// Checks whether a fileSummary or file path is audio/video that we can play,
|
||||
// based on the file extension
|
||||
function isPlayable (file) {
|
||||
return isVideo(file) || isAudio(file)
|
||||
}
|
||||
|
||||
// Checks whether a fileSummary or file path is playable video
|
||||
function isVideo (file) {
|
||||
var ext = path.extname(file.name).toLowerCase()
|
||||
return [
|
||||
'.avi',
|
||||
'.m4v',
|
||||
@@ -27,21 +27,36 @@ function isVideo (file) {
|
||||
'.ogv',
|
||||
'.webm',
|
||||
'.wmv'
|
||||
].includes(ext)
|
||||
].includes(getFileExtension(file))
|
||||
}
|
||||
|
||||
// Checks whether a fileSummary or file path is playable audio
|
||||
function isAudio (file) {
|
||||
var ext = path.extname(file.name).toLowerCase()
|
||||
return [
|
||||
'.aac',
|
||||
'.ac3',
|
||||
'.mp3',
|
||||
'.ogg',
|
||||
'.wav'
|
||||
].includes(ext)
|
||||
].includes(getFileExtension(file))
|
||||
}
|
||||
|
||||
function isPlayableTorrent (torrentSummary) {
|
||||
// Checks if the argument is either:
|
||||
// - a string that's a valid filename ending in .torrent
|
||||
// - a file object where obj.name is ends in .torrent
|
||||
// - a string that's a magnet link (magnet://...)
|
||||
function isTorrent (file) {
|
||||
var isTorrentFile = getFileExtension(file) === '.torrent'
|
||||
var isMagnet = typeof file === 'string' && /^(stream-)?magnet:/.test(file)
|
||||
return isTorrentFile || isMagnet
|
||||
}
|
||||
|
||||
function getFileExtension (file) {
|
||||
var name = typeof file === 'string' ? file : file.name
|
||||
return path.extname(name).toLowerCase()
|
||||
}
|
||||
|
||||
function isPlayableTorrentSummary (torrentSummary) {
|
||||
return torrentSummary.files && torrentSummary.files.some(isPlayable)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,8 @@ module.exports = {
|
||||
getPosterPath,
|
||||
getTorrentPath,
|
||||
getByKey,
|
||||
getTorrentID
|
||||
getTorrentID,
|
||||
getFileOrFolder
|
||||
}
|
||||
|
||||
var path = require('path')
|
||||
@@ -43,3 +44,13 @@ function getByKey (state, torrentKey) {
|
||||
return state.saved.torrents.find((x) =>
|
||||
x.torrentKey === torrentKey || x.infoHash === torrentKey)
|
||||
}
|
||||
|
||||
// Returns the path to either the file (in a single-file torrent) or the root
|
||||
// folder (in multi-file torrent)
|
||||
// WARNING: assumes that multi-file torrents consist of a SINGLE folder.
|
||||
// TODO: make this assumption explicit, enforce it in the `create-torrent`
|
||||
// module. Store root folder explicitly to avoid hacky path processing below.
|
||||
function getFileOrFolder (torrentSummary) {
|
||||
var ts = torrentSummary
|
||||
return path.join(ts.path, ts.files[0].path.split('/')[0])
|
||||
}
|
||||
|
||||
@@ -326,7 +326,6 @@ table {
|
||||
}
|
||||
|
||||
.create-torrent input.torrent-is-private {
|
||||
width: initial;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@@ -404,7 +403,7 @@ button.button-raised:active {
|
||||
* OTHER FORM ELEMENT DEFAULTS
|
||||
*/
|
||||
|
||||
input {
|
||||
input[type='text'] {
|
||||
background: transparent;
|
||||
width: 300px;
|
||||
padding: 6px;
|
||||
|
||||
230
renderer/main.js
230
renderer/main.js
@@ -6,7 +6,6 @@ crashReporter.init()
|
||||
const dragDrop = require('drag-drop')
|
||||
const electron = require('electron')
|
||||
const mainLoop = require('main-loop')
|
||||
const path = require('path')
|
||||
|
||||
const createElement = require('virtual-dom/create-element')
|
||||
const diff = require('virtual-dom/diff')
|
||||
@@ -18,14 +17,14 @@ const telemetry = require('./lib/telemetry')
|
||||
const sound = require('./lib/sound')
|
||||
const State = require('./lib/state')
|
||||
const TorrentPlayer = require('./lib/torrent-player')
|
||||
const TorrentSummary = require('./lib/torrent-summary')
|
||||
|
||||
const MediaController = require('./controllers/media-controller')
|
||||
const UpdateController = require('./controllers/update-controller')
|
||||
const PrefsController = require('./controllers/prefs-controller')
|
||||
const TorrentListController = require('./controllers/torrent-list-controller')
|
||||
const PlaybackController = require('./controllers/playback-controller')
|
||||
const SubtitlesController = require('./controllers/subtitles-controller')
|
||||
const TorrentListController = require('./controllers/torrent-list-controller')
|
||||
const TorrentController = require('./controllers/torrent-controller')
|
||||
|
||||
// Yo-yo pattern: state object lives here and percolates down thru all the views.
|
||||
// Events come back up from the views via dispatch(...)
|
||||
@@ -60,9 +59,10 @@ function onState (err, _state) {
|
||||
media: new MediaController(state),
|
||||
update: new UpdateController(state),
|
||||
prefs: new PrefsController(state, config),
|
||||
torrentList: new TorrentListController(state),
|
||||
playback: new PlaybackController(state, config, update),
|
||||
subtitles: new SubtitlesController(state)
|
||||
subtitles: new SubtitlesController(state),
|
||||
torrentList: new TorrentListController(state),
|
||||
torrent: new TorrentController(state)
|
||||
}
|
||||
|
||||
// Add first page to location history
|
||||
@@ -178,7 +178,8 @@ const dispatchHandlers = {
|
||||
'createTorrent': (options) => controllers.torrentList.createTorrent(options),
|
||||
'toggleTorrent': (infoHash) => controllers.torrentList.toggleTorrent(infoHash),
|
||||
'toggleTorrentFile': (infoHash, index) => controllers.torrentList.toggleTorrentFile(infoHash, index),
|
||||
'deleteTorrent': (infoHash) => controllers.torrentList.deleteTorrent(infoHash),
|
||||
'confirmDeleteTorrent': (infoHash, deleteData) => controllers.torrentList.confirmDeleteTorrent(infoHash, deleteData),
|
||||
'deleteTorrent': (infoHash, deleteData) => controllers.torrentList.deleteTorrent(infoHash, deleteData),
|
||||
'toggleSelectTorrent': (infoHash) => controllers.torrentList.toggleSelectTorrent(infoHash),
|
||||
'openTorrentContextMenu': (infoHash) => controllers.torrentList.openTorrentContextMenu(infoHash),
|
||||
'startTorrentingSummary': (torrentSummary) =>
|
||||
@@ -239,7 +240,9 @@ const dispatchHandlers = {
|
||||
'onOpen': onOpen,
|
||||
'error': onError,
|
||||
'uncaughtError': (proc, err) => telemetry.logUncaughtError(proc, err),
|
||||
'saveState': () => State.save(state)
|
||||
'saveState': () => State.save(state),
|
||||
'saveStateThrottled': () => State.saveThrottled(state),
|
||||
'update': () => {} // No-op, just trigger an update
|
||||
}
|
||||
|
||||
// Events from the UI never modify state directly. Instead they call dispatch()
|
||||
@@ -269,18 +272,19 @@ function setupIpc () {
|
||||
|
||||
ipcRenderer.on('fullscreenChanged', onFullscreenChanged)
|
||||
|
||||
ipcRenderer.on('wt-infohash', (e, ...args) => torrentInfoHash(...args))
|
||||
ipcRenderer.on('wt-metadata', (e, ...args) => torrentMetadata(...args))
|
||||
ipcRenderer.on('wt-done', (e, ...args) => torrentDone(...args))
|
||||
ipcRenderer.on('wt-warning', (e, ...args) => torrentWarning(...args))
|
||||
ipcRenderer.on('wt-error', (e, ...args) => torrentError(...args))
|
||||
var tc = controllers.torrent
|
||||
ipcRenderer.on('wt-infohash', (e, ...args) => tc.torrentInfoHash(...args))
|
||||
ipcRenderer.on('wt-metadata', (e, ...args) => tc.torrentMetadata(...args))
|
||||
ipcRenderer.on('wt-done', (e, ...args) => tc.torrentDone(...args))
|
||||
ipcRenderer.on('wt-warning', (e, ...args) => tc.torrentWarning(...args))
|
||||
ipcRenderer.on('wt-error', (e, ...args) => tc.torrentError(...args))
|
||||
|
||||
ipcRenderer.on('wt-progress', (e, ...args) => torrentProgress(...args))
|
||||
ipcRenderer.on('wt-file-modtimes', (e, ...args) => torrentFileModtimes(...args))
|
||||
ipcRenderer.on('wt-file-saved', (e, ...args) => torrentFileSaved(...args))
|
||||
ipcRenderer.on('wt-poster', (e, ...args) => torrentPosterSaved(...args))
|
||||
ipcRenderer.on('wt-audio-metadata', (e, ...args) => torrentAudioMetadata(...args))
|
||||
ipcRenderer.on('wt-server-running', (e, ...args) => torrentServerRunning(...args))
|
||||
ipcRenderer.on('wt-progress', (e, ...args) => tc.torrentProgress(...args))
|
||||
ipcRenderer.on('wt-file-modtimes', (e, ...args) => tc.torrentFileModtimes(...args))
|
||||
ipcRenderer.on('wt-file-saved', (e, ...args) => tc.torrentFileSaved(...args))
|
||||
ipcRenderer.on('wt-poster', (e, ...args) => tc.torrentPosterSaved(...args))
|
||||
ipcRenderer.on('wt-audio-metadata', (e, ...args) => tc.torrentAudioMetadata(...args))
|
||||
ipcRenderer.on('wt-server-running', (e, ...args) => tc.torrentServerRunning(...args))
|
||||
|
||||
ipcRenderer.on('wt-uncaught-error', (e, err) => telemetry.logUncaughtError('webtorrent', err))
|
||||
|
||||
@@ -325,177 +329,6 @@ function resumeTorrents () {
|
||||
.forEach((torrentSummary) => controllers.torrentList.startTorrentingSummary(torrentSummary))
|
||||
}
|
||||
|
||||
function isTorrent (file) {
|
||||
var name = typeof file === 'string' ? file : file.name
|
||||
var isTorrentFile = path.extname(name).toLowerCase() === '.torrent'
|
||||
var isMagnet = typeof file === 'string' && /^(stream-)?magnet:/.test(file)
|
||||
return isTorrentFile || isMagnet
|
||||
}
|
||||
|
||||
// Gets a torrent summary {name, infoHash, status} from state.saved.torrents
|
||||
// Returns undefined if we don't know that infoHash
|
||||
function getTorrentSummary (torrentKey) {
|
||||
return TorrentSummary.getByKey(state, torrentKey)
|
||||
}
|
||||
|
||||
function torrentInfoHash (torrentKey, infoHash) {
|
||||
var torrentSummary = getTorrentSummary(torrentKey)
|
||||
console.log('got infohash for %s torrent %s',
|
||||
torrentSummary ? 'existing' : 'new', torrentKey)
|
||||
|
||||
if (!torrentSummary) {
|
||||
// Check if an existing (non-active) torrent has the same info hash
|
||||
if (state.saved.torrents.find((t) => t.infoHash === infoHash)) {
|
||||
ipcRenderer.send('wt-stop-torrenting', infoHash)
|
||||
return onError(new Error('Cannot add duplicate torrent'))
|
||||
}
|
||||
|
||||
torrentSummary = {
|
||||
torrentKey: torrentKey,
|
||||
status: 'new'
|
||||
}
|
||||
state.saved.torrents.unshift(torrentSummary)
|
||||
sound.play('ADD')
|
||||
}
|
||||
|
||||
torrentSummary.infoHash = infoHash
|
||||
update()
|
||||
}
|
||||
|
||||
function torrentWarning (torrentKey, message) {
|
||||
onWarning(message)
|
||||
}
|
||||
|
||||
function torrentError (torrentKey, message) {
|
||||
// TODO: WebTorrent needs semantic errors
|
||||
if (message.startsWith('Cannot add duplicate torrent')) {
|
||||
// Remove infohash from the message
|
||||
message = 'Cannot add duplicate torrent'
|
||||
}
|
||||
onError(message)
|
||||
|
||||
var torrentSummary = getTorrentSummary(torrentKey)
|
||||
if (torrentSummary) {
|
||||
console.log('Pausing torrent %s due to error: %s', torrentSummary.infoHash, message)
|
||||
torrentSummary.status = 'paused'
|
||||
update()
|
||||
}
|
||||
}
|
||||
|
||||
function torrentMetadata (torrentKey, torrentInfo) {
|
||||
// Summarize torrent
|
||||
var torrentSummary = getTorrentSummary(torrentKey)
|
||||
torrentSummary.status = 'downloading'
|
||||
torrentSummary.name = torrentSummary.displayName || torrentInfo.name
|
||||
torrentSummary.path = torrentInfo.path
|
||||
torrentSummary.magnetURI = torrentInfo.magnetURI
|
||||
// TODO: make torrentInfo immutable, save separately as torrentSummary.info
|
||||
// For now, check whether torrentSummary.files has already been set:
|
||||
var hasDetailedFileInfo = torrentSummary.files && torrentSummary.files[0].path
|
||||
if (!hasDetailedFileInfo) {
|
||||
torrentSummary.files = torrentInfo.files
|
||||
}
|
||||
if (!torrentSummary.selections) {
|
||||
torrentSummary.selections = torrentSummary.files.map((x) => true)
|
||||
}
|
||||
torrentSummary.defaultPlayFileIndex = TorrentPlayer.pickFileToPlay(torrentInfo.files)
|
||||
update()
|
||||
|
||||
// Save the .torrent file, if it hasn't been saved already
|
||||
if (!torrentSummary.torrentFileName) ipcRenderer.send('wt-save-torrent-file', torrentKey)
|
||||
|
||||
// Auto-generate a poster image, if it hasn't been generated already
|
||||
if (!torrentSummary.posterFileName) ipcRenderer.send('wt-generate-torrent-poster', torrentKey)
|
||||
}
|
||||
|
||||
function torrentDone (torrentKey, torrentInfo) {
|
||||
// Update the torrent summary
|
||||
var torrentSummary = getTorrentSummary(torrentKey)
|
||||
torrentSummary.status = 'seeding'
|
||||
|
||||
// Notify the user that a torrent finished, but only if we actually DL'd at least part of it.
|
||||
// Don't notify if we merely finished verifying data files that were already on disk.
|
||||
if (torrentInfo.bytesReceived > 0) {
|
||||
if (!state.window.isFocused) {
|
||||
state.dock.badge += 1
|
||||
}
|
||||
showDoneNotification(torrentSummary)
|
||||
ipcRenderer.send('downloadFinished', getTorrentPath(torrentSummary))
|
||||
}
|
||||
|
||||
update()
|
||||
}
|
||||
|
||||
function torrentProgress (progressInfo) {
|
||||
// Overall progress across all active torrents, 0 to 1
|
||||
var progress = progressInfo.progress
|
||||
var hasActiveTorrents = progressInfo.hasActiveTorrents
|
||||
|
||||
// Hide progress bar when client has no torrents, or progress is 100%
|
||||
// TODO: isn't this equivalent to: if (progress === 1) ?
|
||||
if (!hasActiveTorrents || progress === 1) {
|
||||
progress = -1
|
||||
}
|
||||
|
||||
// Show progress bar under the WebTorrent taskbar icon, on OSX
|
||||
state.dock.progress = progress
|
||||
|
||||
// Update progress for each individual torrent
|
||||
progressInfo.torrents.forEach(function (p) {
|
||||
var torrentSummary = getTorrentSummary(p.torrentKey)
|
||||
if (!torrentSummary) {
|
||||
console.log('warning: got progress for missing torrent %s', p.torrentKey)
|
||||
return
|
||||
}
|
||||
torrentSummary.progress = p
|
||||
})
|
||||
|
||||
// TODO: Find an efficient way to re-enable this line, which allows subtitle
|
||||
// files which are completed after a video starts to play to be added
|
||||
// dynamically to the list of subtitles.
|
||||
// checkForSubtitles()
|
||||
|
||||
update()
|
||||
}
|
||||
|
||||
function torrentFileModtimes (torrentKey, fileModtimes) {
|
||||
var torrentSummary = getTorrentSummary(torrentKey)
|
||||
torrentSummary.fileModtimes = fileModtimes
|
||||
State.saveThrottled(state)
|
||||
}
|
||||
|
||||
function torrentFileSaved (torrentKey, torrentFileName) {
|
||||
console.log('torrent file saved %s: %s', torrentKey, torrentFileName)
|
||||
var torrentSummary = getTorrentSummary(torrentKey)
|
||||
torrentSummary.torrentFileName = torrentFileName
|
||||
State.saveThrottled(state)
|
||||
}
|
||||
|
||||
function torrentPosterSaved (torrentKey, posterFileName) {
|
||||
var torrentSummary = getTorrentSummary(torrentKey)
|
||||
torrentSummary.posterFileName = posterFileName
|
||||
State.saveThrottled(state)
|
||||
}
|
||||
|
||||
function torrentAudioMetadata (infoHash, index, info) {
|
||||
var torrentSummary = getTorrentSummary(infoHash)
|
||||
var fileSummary = torrentSummary.files[index]
|
||||
fileSummary.audioInfo = info
|
||||
update()
|
||||
}
|
||||
|
||||
function torrentServerRunning (serverInfo) {
|
||||
state.server = serverInfo
|
||||
}
|
||||
|
||||
function getTorrentPath (torrentSummary) {
|
||||
var itemPath = path.join(torrentSummary.path, torrentSummary.files[0].path)
|
||||
if (torrentSummary.files.length > 1) {
|
||||
itemPath = path.dirname(itemPath)
|
||||
}
|
||||
return itemPath
|
||||
}
|
||||
|
||||
// Set window dimensions to match video dimensions or fill the screen
|
||||
function setDimensions (dimensions) {
|
||||
// Don't modify the window size if it's already maximized
|
||||
@@ -535,19 +368,6 @@ function setDimensions (dimensions) {
|
||||
state.playing.aspectRatio = aspectRatio
|
||||
}
|
||||
|
||||
function showDoneNotification (torrent) {
|
||||
var notif = new window.Notification('Download Complete', {
|
||||
body: torrent.name,
|
||||
silent: true
|
||||
})
|
||||
|
||||
notif.onclick = function () {
|
||||
ipcRenderer.send('show')
|
||||
}
|
||||
|
||||
sound.play('DONE')
|
||||
}
|
||||
|
||||
// Called when the user adds files (.torrent, files to seed, subtitles) to the app
|
||||
// via any method (drag-drop, drag to app icon, command line)
|
||||
function onOpen (files) {
|
||||
@@ -560,7 +380,7 @@ function onOpen (files) {
|
||||
var subtitles = files.filter(controllers.subtitles.isSubtitle)
|
||||
|
||||
if (state.location.url() === 'home' || subtitles.length === 0) {
|
||||
if (files.every(isTorrent)) {
|
||||
if (files.every(TorrentPlayer.isTorrent)) {
|
||||
if (state.location.url() !== 'home') {
|
||||
backToList()
|
||||
}
|
||||
@@ -588,10 +408,6 @@ function onError (err) {
|
||||
update()
|
||||
}
|
||||
|
||||
function onWarning (err) {
|
||||
console.log('warning: %s', err.message || err)
|
||||
}
|
||||
|
||||
function onPaste (e) {
|
||||
if (e.target.tagName.toLowerCase() === 'input') return
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ var Views = {
|
||||
|
||||
var Modals = {
|
||||
'open-torrent-address-modal': require('./open-torrent-address-modal'),
|
||||
'remove-torrent-modal': require('./remove-torrent-modal'),
|
||||
'update-available-modal': require('./update-available-modal'),
|
||||
'unsupported-media-modal': require('./unsupported-media-modal')
|
||||
}
|
||||
|
||||
26
renderer/views/remove-torrent-modal.js
Normal file
26
renderer/views/remove-torrent-modal.js
Normal file
@@ -0,0 +1,26 @@
|
||||
module.exports = RemoveTorrentModal
|
||||
|
||||
var {dispatch, dispatcher} = require('../lib/dispatcher')
|
||||
var hx = require('../lib/hx')
|
||||
|
||||
function RemoveTorrentModal (state) {
|
||||
var message = state.modal.deleteData
|
||||
? 'Are you sure you want to remove this torrent from the list and delete the data file?'
|
||||
: 'Are you sure you want to remove this torrent from the list?'
|
||||
var buttonText = state.modal.deleteData ? 'Remove Data' : 'Remove'
|
||||
|
||||
return hx`
|
||||
<div>
|
||||
<p><strong>${message}</strong></p>
|
||||
<p class='float-right'>
|
||||
<button class='button button-flat' onclick=${dispatcher('exitModal')}>Cancel</button>
|
||||
<button class='button button-raised' onclick=${handleRemove}>${buttonText}</button>
|
||||
</p>
|
||||
</div>
|
||||
`
|
||||
|
||||
function handleRemove () {
|
||||
dispatch('deleteTorrent', state.modal.infoHash, state.modal.deleteData)
|
||||
dispatch('exitModal')
|
||||
}
|
||||
}
|
||||
@@ -148,7 +148,7 @@ function TorrentList (state) {
|
||||
|
||||
// Only show the play button for torrents that contain playable media
|
||||
var playButton
|
||||
if (TorrentPlayer.isPlayableTorrent(torrentSummary)) {
|
||||
if (TorrentPlayer.isPlayableTorrentSummary(torrentSummary)) {
|
||||
playButton = hx`
|
||||
<i.button-round.icon.play
|
||||
title=${playTooltip}
|
||||
@@ -172,7 +172,7 @@ function TorrentList (state) {
|
||||
<i
|
||||
class='icon delete'
|
||||
title='Remove torrent'
|
||||
onclick=${dispatcher('deleteTorrent', infoHash)}>
|
||||
onclick=${dispatcher('confirmDeleteTorrent', infoHash, false)}>
|
||||
close
|
||||
</i>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user