Refactor main.js: torrent list controller
This commit is contained in:
241
renderer/controllers/torrent-list-controller.js
Normal file
241
renderer/controllers/torrent-list-controller.js
Normal file
@@ -0,0 +1,241 @@
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const electron = require('electron')
|
||||
|
||||
const {dispatch} = require('../lib/dispatcher')
|
||||
const State = require('../lib/state')
|
||||
const sound = require('../lib/sound')
|
||||
const TorrentSummary = require('../lib/torrent-summary')
|
||||
|
||||
const ipcRenderer = electron.ipcRenderer
|
||||
|
||||
const instantIoRegex = /^(https:\/\/)?instant\.io\/#/
|
||||
|
||||
// Controls the torrent list: creating, adding, deleting, & manipulating torrents
|
||||
module.exports = class TorrentListController {
|
||||
constructor (state) {
|
||||
this.state = state
|
||||
}
|
||||
|
||||
// Adds a torrent to the list, starts downloading/seeding. TorrentID can be a
|
||||
// magnet URI, infohash, or torrent file: https://github.com/feross/webtorrent#clientaddtorrentid-opts-function-ontorrent-torrent-
|
||||
addTorrent (torrentId) {
|
||||
if (torrentId.path) {
|
||||
// Use path string instead of W3C File object
|
||||
torrentId = torrentId.path
|
||||
}
|
||||
// Allow a instant.io link to be pasted
|
||||
// TODO: remove this once support is added to webtorrent core
|
||||
if (typeof torrentId === 'string' && instantIoRegex.test(torrentId)) {
|
||||
torrentId = torrentId.slice(torrentId.indexOf('#') + 1)
|
||||
}
|
||||
|
||||
var torrentKey = this.state.nextTorrentKey++
|
||||
var path = this.state.saved.prefs.downloadPath
|
||||
|
||||
ipcRenderer.send('wt-start-torrenting', torrentKey, torrentId, path)
|
||||
|
||||
dispatch('backToList')
|
||||
}
|
||||
|
||||
// Shows the Create Torrent page with options to seed a given file or folder
|
||||
showCreateTorrent (files) {
|
||||
// Files will either be an array of file objects, which we can send directly
|
||||
// to the create-torrent screen
|
||||
if (files.length === 0 || typeof files[0] !== 'string') {
|
||||
this.state.location.go({
|
||||
url: 'create-torrent',
|
||||
files: files
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// ... or it will be an array of mixed file and folder paths. We have to walk
|
||||
// through all the folders and find the files
|
||||
findFilesRecursive(files, (allFiles) => this.showCreateTorrent(allFiles))
|
||||
}
|
||||
|
||||
// Creates a new torrent and start seeeding
|
||||
createTorrent (options) {
|
||||
var state = this.state
|
||||
var torrentKey = state.nextTorrentKey++
|
||||
ipcRenderer.send('wt-create-torrent', torrentKey, options)
|
||||
state.location.backToFirst(function () {
|
||||
state.location.clearForward('create-torrent')
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: use torrentKey, not infoHash
|
||||
toggleTorrent (infoHash) {
|
||||
var torrentSummary = TorrentSummary.getByKey(this.state, infoHash)
|
||||
if (torrentSummary.status === 'paused') {
|
||||
torrentSummary.status = 'new'
|
||||
ipcRenderer.send('wt-start-torrenting', torrentSummary)
|
||||
sound.play('ENABLE')
|
||||
} else {
|
||||
torrentSummary.status = 'paused'
|
||||
ipcRenderer.send('wt-stop-torrenting', torrentSummary.infoHash)
|
||||
sound.play('DISABLE')
|
||||
}
|
||||
}
|
||||
|
||||
toggleTorrentFile (infoHash, index) {
|
||||
var torrentSummary = TorrentSummary.getByKey(this.state, infoHash)
|
||||
torrentSummary.selections[index] = !torrentSummary.selections[index]
|
||||
|
||||
// Let the WebTorrent process know to start or stop fetching that file
|
||||
ipcRenderer.send('wt-select-files', infoHash, torrentSummary.selections)
|
||||
}
|
||||
|
||||
// TODO: use torrentKey, not infoHash
|
||||
deleteTorrent (infoHash, deleteData) {
|
||||
var state = this.state
|
||||
|
||||
ipcRenderer.send('wt-stop-torrenting', infoHash)
|
||||
|
||||
if (deleteData) {
|
||||
var torrentSummary = TorrentSummary.getByKey(state, infoHash)
|
||||
moveItemToTrash(torrentSummary)
|
||||
}
|
||||
|
||||
var index = state.saved.torrents.findIndex((x) => x.infoHash === infoHash)
|
||||
if (index > -1) state.saved.torrents.splice(index, 1)
|
||||
State.saveThrottled(state)
|
||||
state.location.clearForward('player') // prevent user from going forward to a deleted torrent
|
||||
sound.play('DELETE')
|
||||
}
|
||||
|
||||
toggleSelectTorrent (infoHash) {
|
||||
if (this.state.selectedInfoHash === infoHash) {
|
||||
this.state.selectedInfoHash = null
|
||||
} else {
|
||||
this.state.selectedInfoHash = infoHash
|
||||
}
|
||||
}
|
||||
|
||||
openTorrentContextMenu (infoHash) {
|
||||
var torrentSummary = TorrentSummary.getByKey(this.state, infoHash)
|
||||
var menu = new electron.remote.Menu()
|
||||
|
||||
menu.append(new electron.remote.MenuItem({
|
||||
label: 'Remove From List',
|
||||
click: () => this.deleteTorrent(
|
||||
torrentSummary.infoHash, false)
|
||||
}))
|
||||
|
||||
menu.append(new electron.remote.MenuItem({
|
||||
label: 'Remove Data File',
|
||||
click: () => this.deleteTorrent(
|
||||
torrentSummary.infoHash, true)
|
||||
}))
|
||||
|
||||
menu.append(new electron.remote.MenuItem({
|
||||
type: 'separator'
|
||||
}))
|
||||
|
||||
if (torrentSummary.files) {
|
||||
menu.append(new electron.remote.MenuItem({
|
||||
label: process.platform === 'darwin' ? 'Show in Finder' : 'Show in Folder',
|
||||
click: () => showItemInFolder(torrentSummary)
|
||||
}))
|
||||
menu.append(new electron.remote.MenuItem({
|
||||
type: 'separator'
|
||||
}))
|
||||
}
|
||||
|
||||
menu.append(new electron.remote.MenuItem({
|
||||
label: 'Copy Magnet Link to Clipboard',
|
||||
click: () => electron.clipboard.writeText(torrentSummary.magnetURI)
|
||||
}))
|
||||
|
||||
menu.append(new electron.remote.MenuItem({
|
||||
label: 'Copy Instant.io Link to Clipboard',
|
||||
click: () => electron.clipboard.writeText(`https://instant.io/#${torrentSummary.infoHash}`)
|
||||
}))
|
||||
|
||||
menu.append(new electron.remote.MenuItem({
|
||||
label: 'Save Torrent File As...',
|
||||
click: () => saveTorrentFileAs(torrentSummary)
|
||||
}))
|
||||
|
||||
menu.popup(electron.remote.getCurrentWindow())
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively finds {name, path, size} for all files in a folder
|
||||
// Calls `cb` on success, calls `onError` on failure
|
||||
function findFilesRecursive (paths, cb) {
|
||||
if (paths.length > 1) {
|
||||
var numComplete = 0
|
||||
var ret = []
|
||||
paths.forEach(function (path) {
|
||||
findFilesRecursive([path], function (fileObjs) {
|
||||
ret = ret.concat(fileObjs)
|
||||
if (++numComplete === paths.length) {
|
||||
ret.sort((a, b) => a.path < b.path ? -1 : a.path > b.path)
|
||||
cb(ret)
|
||||
}
|
||||
})
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var fileOrFolder = paths[0]
|
||||
fs.stat(fileOrFolder, function (err, stat) {
|
||||
if (err) return dispatch('onError', err)
|
||||
|
||||
// Files: return name, path, and size
|
||||
if (!stat.isDirectory()) {
|
||||
var filePath = fileOrFolder
|
||||
return cb([{
|
||||
name: path.basename(filePath),
|
||||
path: filePath,
|
||||
size: stat.size
|
||||
}])
|
||||
}
|
||||
|
||||
// Folders: recurse, make a list of all the files
|
||||
var folderPath = fileOrFolder
|
||||
fs.readdir(folderPath, function (err, fileNames) {
|
||||
if (err) return dispatch('onError', err)
|
||||
var paths = fileNames.map((fileName) => path.join(folderPath, fileName))
|
||||
findFilesRecursive(paths, cb)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Delete all files in a torren
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
function showItemInFolder (torrentSummary) {
|
||||
ipcRenderer.send('showItemInFolder', TorrentSummary.getTorrentPath(torrentSummary))
|
||||
}
|
||||
|
||||
function saveTorrentFileAs (torrentSummary) {
|
||||
var downloadPath = this.state.saved.prefs.downloadPath
|
||||
var newFileName = `${path.parse(torrentSummary.name).name}.torrent`
|
||||
var opts = {
|
||||
title: 'Save Torrent File',
|
||||
defaultPath: path.join(downloadPath, newFileName),
|
||||
filters: [
|
||||
{ name: 'Torrent Files', extensions: ['torrent'] },
|
||||
{ name: 'All Files', extensions: ['*'] }
|
||||
]
|
||||
}
|
||||
electron.remote.dialog.showSaveDialog(electron.remote.getCurrentWindow(), opts, function (savePath) {
|
||||
var torrentPath = TorrentSummary.getTorrentPath(torrentSummary)
|
||||
fs.readFile(torrentPath, function (err, torrentFile) {
|
||||
if (err) return dispatch('onError', err)
|
||||
fs.writeFile(savePath, torrentFile, function (err) {
|
||||
if (err) return dispatch('onError', err)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -116,6 +116,12 @@ function getApproxNumTorrents (state) {
|
||||
|
||||
// An uncaught error happened in the main process or in one of the windows
|
||||
function logUncaughtError (procName, err) {
|
||||
console.error('uncaught error', procName, err)
|
||||
|
||||
// Not initialized yet? Ignore.
|
||||
// Hopefully uncaught errors immediately on startup are fixed in dev
|
||||
if (!telemetry) return
|
||||
|
||||
var message, stack
|
||||
if (typeof err === 'string') {
|
||||
message = err
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
module.exports = {
|
||||
getPosterPath,
|
||||
getTorrentPath
|
||||
getTorrentPath,
|
||||
getByKey,
|
||||
getTorrentID
|
||||
}
|
||||
|
||||
var path = require('path')
|
||||
@@ -22,3 +24,22 @@ function getPosterPath (torrentSummary) {
|
||||
// Backslashes in URLS in CSS cause bizarre string encoding issues
|
||||
return posterPath.replace(/\\/g, '/')
|
||||
}
|
||||
|
||||
// Expects a torrentSummary
|
||||
// Returns a torrentID: filename, magnet URI, or infohash
|
||||
function getTorrentID (torrentSummary) {
|
||||
var s = torrentSummary
|
||||
if (s.torrentFileName) { // Load torrent file from disk
|
||||
return getTorrentPath(s)
|
||||
} else { // Load torrent from DHT
|
||||
return s.magnetURI || s.infoHash
|
||||
}
|
||||
}
|
||||
|
||||
// Expects a torrentKey or infoHash
|
||||
// Returns the corresponding torrentSummary, or undefined
|
||||
function getByKey (state, torrentKey) {
|
||||
if (!torrentKey) return undefined
|
||||
return state.saved.torrents.find((x) =>
|
||||
x.torrentKey === torrentKey || x.infoHash === torrentKey)
|
||||
}
|
||||
|
||||
483
renderer/main.js
483
renderer/main.js
@@ -26,6 +26,7 @@ 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')
|
||||
|
||||
// Yo-yo pattern: state object lives here and percolates down thru all the views.
|
||||
// Events come back up from the views via dispatch(...)
|
||||
@@ -59,7 +60,8 @@ function onState (err, _state) {
|
||||
controllers = {
|
||||
media: new MediaController(state),
|
||||
update: new UpdateController(state),
|
||||
prefs: new PrefsController(state, config)
|
||||
prefs: new PrefsController(state, config),
|
||||
torrentList: new TorrentListController(state)
|
||||
}
|
||||
|
||||
// Add first page to location history
|
||||
@@ -170,68 +172,44 @@ function dispatch (action, ...args) {
|
||||
console.log('dispatch: %s %o', action, args)
|
||||
}
|
||||
|
||||
if (action === 'onOpen') {
|
||||
onOpen(args[0] /* files */)
|
||||
}
|
||||
if (action === 'addTorrent') {
|
||||
addTorrent(args[0] /* torrent */)
|
||||
}
|
||||
// Torrent list: creating, deleting, selecting torrents
|
||||
if (action === 'openTorrentFile') {
|
||||
ipcRenderer.send('openTorrentFile') /* open torrent file */
|
||||
}
|
||||
if (action === 'openFiles') {
|
||||
ipcRenderer.send('openFiles') /* add files with dialog */
|
||||
}
|
||||
if (action === 'showCreateTorrent') {
|
||||
showCreateTorrent(args[0] /* paths */)
|
||||
}
|
||||
if (action === 'openTorrentAddress') {
|
||||
state.modal = { id: 'open-torrent-address-modal' }
|
||||
}
|
||||
if (action === 'createTorrent') {
|
||||
createTorrent(args[0] /* options */)
|
||||
if (action === 'addTorrent') {
|
||||
controllers.torrentList.addTorrent(args[0] /* torrent */)
|
||||
}
|
||||
if (action === 'openItem') {
|
||||
openItem(args[0] /* infoHash */, args[1] /* index */)
|
||||
if (action === 'showCreateTorrent') {
|
||||
controllers.torrentList.showCreateTorrent(args[0] /* paths */)
|
||||
}
|
||||
if (action === 'createTorrent') {
|
||||
controllers.torrentList.createTorrent(args[0] /* options */)
|
||||
}
|
||||
if (action === 'toggleTorrent') {
|
||||
toggleTorrent(args[0] /* infoHash */)
|
||||
}
|
||||
if (action === 'deleteTorrent') {
|
||||
deleteTorrent(args[0] /* infoHash */)
|
||||
}
|
||||
if (action === 'toggleSelectTorrent') {
|
||||
toggleSelectTorrent(args[0] /* infoHash */)
|
||||
controllers.torrentList.toggleTorrent(args[0] /* infoHash */)
|
||||
}
|
||||
if (action === 'toggleTorrentFile') {
|
||||
toggleTorrentFile(args[0] /* infoHash */, args[1] /* index */)
|
||||
controllers.torrentList.toggleTorrentFile(args[0] /* infoHash */, args[1] /* index */)
|
||||
}
|
||||
if (action === 'deleteTorrent') {
|
||||
controllers.torrentList.deleteTorrent(args[0] /* infoHash */)
|
||||
}
|
||||
if (action === 'toggleSelectTorrent') {
|
||||
controllers.torrentList.toggleSelectTorrent(args[0] /* infoHash */)
|
||||
}
|
||||
if (action === 'openTorrentContextMenu') {
|
||||
openTorrentContextMenu(args[0] /* infoHash */)
|
||||
controllers.torrentList.openTorrentContextMenu(args[0] /* infoHash */)
|
||||
}
|
||||
if (action === 'toggleCastMenu') {
|
||||
lazyLoadCast().toggleMenu(args[0] /* deviceType */)
|
||||
}
|
||||
if (action === 'selectCastDevice') {
|
||||
lazyLoadCast().selectDevice(args[0] /* index */)
|
||||
}
|
||||
if (action === 'stopCasting') {
|
||||
lazyLoadCast().stop()
|
||||
}
|
||||
if (action === 'setDimensions') {
|
||||
setDimensions(args[0] /* dimensions */)
|
||||
}
|
||||
if (action === 'backToList') {
|
||||
backToList()
|
||||
}
|
||||
if (action === 'escapeBack') {
|
||||
escapeBack()
|
||||
}
|
||||
if (action === 'back') {
|
||||
state.location.back()
|
||||
}
|
||||
if (action === 'forward') {
|
||||
state.location.forward()
|
||||
|
||||
// Playback
|
||||
if (action === 'openItem') {
|
||||
openItem(args[0] /* infoHash */, args[1] /* index */)
|
||||
}
|
||||
if (action === 'playPause') {
|
||||
playPause()
|
||||
@@ -284,30 +262,72 @@ function dispatch (action, ...args) {
|
||||
if (action === 'vlcNotFound') {
|
||||
controllers.media.vlcNotFound()
|
||||
}
|
||||
if (action === 'toggleFullScreen') {
|
||||
ipcRenderer.send('toggleFullScreen', args[0] /* optional bool */)
|
||||
|
||||
// Casting: Chromecast, Airplay, etc
|
||||
if (action === 'toggleCastMenu') {
|
||||
lazyLoadCast().toggleMenu(args[0] /* deviceType */)
|
||||
}
|
||||
if (action === 'exitModal') {
|
||||
state.modal = null
|
||||
if (action === 'selectCastDevice') {
|
||||
lazyLoadCast().selectDevice(args[0] /* index */)
|
||||
}
|
||||
if (action === 'stopCasting') {
|
||||
lazyLoadCast().stop()
|
||||
}
|
||||
|
||||
// Preferences screen
|
||||
if (action === 'preferences') {
|
||||
controllers.prefs.show()
|
||||
}
|
||||
if (action === 'updatePreferences') {
|
||||
controllers.prefs.update(args[0] /* key */, args[1] /* value */)
|
||||
}
|
||||
|
||||
// Update (check for new versions on Linux, where there's no auto updater)
|
||||
if (action === 'updateAvailable') {
|
||||
controllers.update.updateAvailable(args[0] /* version */)
|
||||
}
|
||||
if (action === 'skipVersion') {
|
||||
controllers.update.skipVersion(args[0] /* version */)
|
||||
}
|
||||
if (action === 'saveState') {
|
||||
State.save(state)
|
||||
|
||||
// Navigation between screens (back, forward, ESC, etc)
|
||||
if (action === 'exitModal') {
|
||||
state.modal = null
|
||||
}
|
||||
if (action === 'backToList') {
|
||||
backToList()
|
||||
}
|
||||
if (action === 'escapeBack') {
|
||||
escapeBack()
|
||||
}
|
||||
if (action === 'back') {
|
||||
state.location.back()
|
||||
}
|
||||
if (action === 'forward') {
|
||||
state.location.forward()
|
||||
}
|
||||
|
||||
// Controlling the window
|
||||
if (action === 'setDimensions') {
|
||||
setDimensions(args[0] /* dimensions */)
|
||||
}
|
||||
if (action === 'toggleFullScreen') {
|
||||
ipcRenderer.send('toggleFullScreen', args[0] /* optional bool */)
|
||||
}
|
||||
if (action === 'setTitle') {
|
||||
state.window.title = args[0] /* title */
|
||||
}
|
||||
|
||||
// Everything else
|
||||
if (action === 'onOpen') {
|
||||
onOpen(args[0] /* files */)
|
||||
}
|
||||
if (action === 'saveState') {
|
||||
State.save(state)
|
||||
}
|
||||
if (action === 'onError') {
|
||||
onError(args[0] /* user-friendly error */)
|
||||
}
|
||||
if (action === 'uncaughtError') {
|
||||
telemetry.logUncaughtError(args[0] /* process */, args[1] /* error */)
|
||||
}
|
||||
@@ -318,6 +338,33 @@ function dispatch (action, ...args) {
|
||||
}
|
||||
}
|
||||
|
||||
// Listen to events from the main and webtorrent processes
|
||||
function setupIpc () {
|
||||
ipcRenderer.on('log', (e, ...args) => console.log(...args))
|
||||
ipcRenderer.on('error', (e, ...args) => console.error(...args))
|
||||
|
||||
ipcRenderer.on('dispatch', (e, ...args) => dispatch(...args))
|
||||
|
||||
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))
|
||||
|
||||
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-uncaught-error', (e, err) => telemetry.logUncaughtError('webtorrent', err))
|
||||
|
||||
ipcRenderer.send('ipcReady')
|
||||
}
|
||||
|
||||
function play () {
|
||||
if (!state.playing.isPaused) return
|
||||
state.playing.isPaused = false
|
||||
@@ -441,39 +488,6 @@ function isCasting () {
|
||||
state.playing.location === 'dlna'
|
||||
}
|
||||
|
||||
function setupIpc () {
|
||||
ipcRenderer.on('log', (e, ...args) => console.log(...args))
|
||||
ipcRenderer.on('error', (e, ...args) => console.error(...args))
|
||||
|
||||
ipcRenderer.on('dispatch', (e, ...args) => dispatch(...args))
|
||||
|
||||
ipcRenderer.on('fullscreenChanged', function (e, isFullScreen) {
|
||||
state.window.isFullScreen = isFullScreen
|
||||
if (!isFullScreen) {
|
||||
// Aspect ratio gets reset in fullscreen mode, so restore it (OS X)
|
||||
ipcRenderer.send('setAspectRatio', state.playing.aspectRatio)
|
||||
}
|
||||
update()
|
||||
})
|
||||
|
||||
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))
|
||||
|
||||
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-uncaught-error', (e, err) => telemetry.logUncaughtError('webtorrent', err))
|
||||
|
||||
ipcRenderer.send('ipcReady')
|
||||
}
|
||||
|
||||
// Starts all torrents that aren't paused on program startup
|
||||
function resumeTorrents () {
|
||||
state.saved.torrents
|
||||
@@ -481,35 +495,6 @@ function resumeTorrents () {
|
||||
.forEach((torrentSummary) => startTorrentingSummary(torrentSummary))
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if (!Array.isArray(files)) files = [ files ]
|
||||
|
||||
if (state.modal) {
|
||||
state.modal = null
|
||||
}
|
||||
|
||||
var subtitles = files.filter(isSubtitle)
|
||||
|
||||
if (state.location.url() === 'home' || subtitles.length === 0) {
|
||||
if (files.every(isTorrent)) {
|
||||
if (state.location.url() !== 'home') {
|
||||
backToList()
|
||||
}
|
||||
// All .torrent files? Add them.
|
||||
files.forEach(addTorrent)
|
||||
} else {
|
||||
// Show the Create Torrent screen. Let's seed those files.
|
||||
showCreateTorrent(files)
|
||||
}
|
||||
} else if (state.location.url() === 'player') {
|
||||
addSubtitles(subtitles, true)
|
||||
}
|
||||
|
||||
update()
|
||||
}
|
||||
|
||||
function isTorrent (file) {
|
||||
var name = typeof file === 'string' ? file : file.name
|
||||
var isTorrentFile = path.extname(name).toLowerCase() === '.torrent'
|
||||
@@ -526,28 +511,7 @@ function isSubtitle (file) {
|
||||
// Gets a torrent summary {name, infoHash, status} from state.saved.torrents
|
||||
// Returns undefined if we don't know that infoHash
|
||||
function getTorrentSummary (torrentKey) {
|
||||
if (!torrentKey) return undefined
|
||||
return state.saved.torrents.find((x) =>
|
||||
x.torrentKey === torrentKey || x.infoHash === torrentKey)
|
||||
}
|
||||
|
||||
// Adds a torrent to the list, starts downloading/seeding. TorrentID can be a
|
||||
// magnet URI, infohash, or torrent file: https://github.com/feross/webtorrent#clientaddtorrentid-opts-function-ontorrent-torrent-
|
||||
var instantIoRegex = /^(https:\/\/)?instant\.io\/#/
|
||||
function addTorrent (torrentId) {
|
||||
backToList()
|
||||
var torrentKey = state.nextTorrentKey++
|
||||
var path = state.saved.prefs.downloadPath
|
||||
if (torrentId.path) {
|
||||
// Use path string instead of W3C File object
|
||||
torrentId = torrentId.path
|
||||
}
|
||||
// Allow a instant.io link to be pasted
|
||||
// TODO: remove this once support is added to webtorrent core
|
||||
if (typeof torrentId === 'string' && instantIoRegex.test(torrentId)) {
|
||||
torrentId = torrentId.slice(torrentId.indexOf('#') + 1)
|
||||
}
|
||||
ipcRenderer.send('wt-start-torrenting', torrentKey, torrentId, path)
|
||||
return TorrentSummary.getByKey(state, torrentKey)
|
||||
}
|
||||
|
||||
function addSubtitles (files, autoSelect) {
|
||||
@@ -661,90 +625,9 @@ function startTorrentingSummary (torrentSummary) {
|
||||
if (!s.torrentKey) s.torrentKey = state.nextTorrentKey++
|
||||
|
||||
// Use Downloads folder by default
|
||||
var path = s.path || state.saved.prefs.downloadPath
|
||||
if (!s.path) s.path = state.saved.prefs.downloadPath
|
||||
|
||||
var torrentID
|
||||
if (s.torrentFileName) { // Load torrent file from disk
|
||||
torrentID = TorrentSummary.getTorrentPath(torrentSummary)
|
||||
} else { // Load torrent from DHT
|
||||
torrentID = s.magnetURI || s.infoHash
|
||||
}
|
||||
|
||||
console.log('start torrenting %s %s', s.torrentKey, torrentID)
|
||||
ipcRenderer.send('wt-start-torrenting', s.torrentKey, torrentID, path, s.fileModtimes, s.selections)
|
||||
}
|
||||
|
||||
//
|
||||
// TORRENT MANAGEMENT
|
||||
// Send commands to the WebTorrent process, handle events
|
||||
//
|
||||
|
||||
// Shows the Create Torrent page with options to seed a given file or folder
|
||||
function showCreateTorrent (files) {
|
||||
// Files will either be an array of file objects, which we can send directly
|
||||
// to the create-torrent screen
|
||||
if (files.length === 0 || typeof files[0] !== 'string') {
|
||||
state.location.go({
|
||||
url: 'create-torrent',
|
||||
files: files
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// ... or it will be an array of mixed file and folder paths. We have to walk
|
||||
// through all the folders and find the files
|
||||
findFilesRecursive(files, showCreateTorrent)
|
||||
}
|
||||
|
||||
// Recursively finds {name, path, size} for all files in a folder
|
||||
// Calls `cb` on success, calls `onError` on failure
|
||||
function findFilesRecursive (paths, cb) {
|
||||
if (paths.length > 1) {
|
||||
var numComplete = 0
|
||||
var ret = []
|
||||
paths.forEach(function (path) {
|
||||
findFilesRecursive([path], function (fileObjs) {
|
||||
ret = ret.concat(fileObjs)
|
||||
if (++numComplete === paths.length) {
|
||||
ret.sort((a, b) => a.path < b.path ? -1 : a.path > b.path)
|
||||
cb(ret)
|
||||
}
|
||||
})
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var fileOrFolder = paths[0]
|
||||
fs.stat(fileOrFolder, function (err, stat) {
|
||||
if (err) return onError(err)
|
||||
|
||||
// Files: return name, path, and size
|
||||
if (!stat.isDirectory()) {
|
||||
var filePath = fileOrFolder
|
||||
return cb([{
|
||||
name: path.basename(filePath),
|
||||
path: filePath,
|
||||
size: stat.size
|
||||
}])
|
||||
}
|
||||
|
||||
// Folders: recurse, make a list of all the files
|
||||
var folderPath = fileOrFolder
|
||||
fs.readdir(folderPath, function (err, fileNames) {
|
||||
if (err) return onError(err)
|
||||
var paths = fileNames.map((fileName) => path.join(folderPath, fileName))
|
||||
findFilesRecursive(paths, cb)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Creates a new torrent and start seeeding
|
||||
function createTorrent (options) {
|
||||
var torrentKey = state.nextTorrentKey++
|
||||
ipcRenderer.send('wt-create-torrent', torrentKey, options)
|
||||
state.location.backToFirst(function () {
|
||||
state.location.clearForward('create-torrent')
|
||||
})
|
||||
ipcRenderer.send('wt-start-torrenting', s)
|
||||
}
|
||||
|
||||
function torrentInfoHash (torrentKey, infoHash) {
|
||||
@@ -1063,96 +946,6 @@ function openItem (infoHash, index) {
|
||||
ipcRenderer.send('openItem', filePath)
|
||||
}
|
||||
|
||||
// TODO: use torrentKey, not infoHash
|
||||
function toggleTorrent (infoHash) {
|
||||
var torrentSummary = getTorrentSummary(infoHash)
|
||||
if (torrentSummary.status === 'paused') {
|
||||
torrentSummary.status = 'new'
|
||||
startTorrentingSummary(torrentSummary)
|
||||
sound.play('ENABLE')
|
||||
} else {
|
||||
torrentSummary.status = 'paused'
|
||||
ipcRenderer.send('wt-stop-torrenting', torrentSummary.infoHash)
|
||||
sound.play('DISABLE')
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: use torrentKey, not infoHash
|
||||
function deleteTorrent (infoHash, deleteData) {
|
||||
ipcRenderer.send('wt-stop-torrenting', infoHash)
|
||||
|
||||
if (deleteData) {
|
||||
var torrentSummary = getTorrentSummary(infoHash)
|
||||
moveItemToTrash(torrentSummary)
|
||||
}
|
||||
|
||||
var index = state.saved.torrents.findIndex((x) => x.infoHash === infoHash)
|
||||
if (index > -1) state.saved.torrents.splice(index, 1)
|
||||
State.saveThrottled(state)
|
||||
state.location.clearForward('player') // prevent user from going forward to a deleted torrent
|
||||
sound.play('DELETE')
|
||||
}
|
||||
|
||||
function toggleSelectTorrent (infoHash) {
|
||||
// toggle selection
|
||||
state.selectedInfoHash = state.selectedInfoHash === infoHash ? null : infoHash
|
||||
update()
|
||||
}
|
||||
|
||||
function toggleTorrentFile (infoHash, index) {
|
||||
var torrentSummary = getTorrentSummary(infoHash)
|
||||
torrentSummary.selections[index] = !torrentSummary.selections[index]
|
||||
|
||||
// Let the WebTorrent process know to start or stop fetching that file
|
||||
ipcRenderer.send('wt-select-files', infoHash, torrentSummary.selections)
|
||||
}
|
||||
|
||||
function openTorrentContextMenu (infoHash) {
|
||||
var torrentSummary = getTorrentSummary(infoHash)
|
||||
var menu = new electron.remote.Menu()
|
||||
|
||||
menu.append(new electron.remote.MenuItem({
|
||||
label: 'Remove From List',
|
||||
click: () => deleteTorrent(torrentSummary.infoHash, false)
|
||||
}))
|
||||
|
||||
menu.append(new electron.remote.MenuItem({
|
||||
label: 'Remove Data File',
|
||||
click: () => deleteTorrent(torrentSummary.infoHash, true)
|
||||
}))
|
||||
|
||||
menu.append(new electron.remote.MenuItem({
|
||||
type: 'separator'
|
||||
}))
|
||||
|
||||
if (torrentSummary.files) {
|
||||
menu.append(new electron.remote.MenuItem({
|
||||
label: process.platform === 'darwin' ? 'Show in Finder' : 'Show in Folder',
|
||||
click: () => showItemInFolder(torrentSummary)
|
||||
}))
|
||||
menu.append(new electron.remote.MenuItem({
|
||||
type: 'separator'
|
||||
}))
|
||||
}
|
||||
|
||||
menu.append(new electron.remote.MenuItem({
|
||||
label: 'Copy Magnet Link to Clipboard',
|
||||
click: () => electron.clipboard.writeText(torrentSummary.magnetURI)
|
||||
}))
|
||||
|
||||
menu.append(new electron.remote.MenuItem({
|
||||
label: 'Copy Instant.io Link to Clipboard',
|
||||
click: () => electron.clipboard.writeText(`https://instant.io/#${torrentSummary.infoHash}`)
|
||||
}))
|
||||
|
||||
menu.append(new electron.remote.MenuItem({
|
||||
label: 'Save Torrent File As...',
|
||||
click: () => saveTorrentFileAs(torrentSummary)
|
||||
}))
|
||||
|
||||
menu.popup(electron.remote.getCurrentWindow())
|
||||
}
|
||||
|
||||
function getTorrentPath (torrentSummary) {
|
||||
var itemPath = path.join(torrentSummary.path, torrentSummary.files[0].path)
|
||||
if (torrentSummary.files.length > 1) {
|
||||
@@ -1161,35 +954,6 @@ function getTorrentPath (torrentSummary) {
|
||||
return itemPath
|
||||
}
|
||||
|
||||
function showItemInFolder (torrentSummary) {
|
||||
ipcRenderer.send('showItemInFolder', getTorrentPath(torrentSummary))
|
||||
}
|
||||
|
||||
function moveItemToTrash (torrentSummary) {
|
||||
ipcRenderer.send('moveItemToTrash', getTorrentPath(torrentSummary))
|
||||
}
|
||||
|
||||
function saveTorrentFileAs (torrentSummary) {
|
||||
var newFileName = `${path.parse(torrentSummary.name).name}.torrent`
|
||||
var opts = {
|
||||
title: 'Save Torrent File',
|
||||
defaultPath: path.join(state.saved.prefs.downloadPath, newFileName),
|
||||
filters: [
|
||||
{ name: 'Torrent Files', extensions: ['torrent'] },
|
||||
{ name: 'All Files', extensions: ['*'] }
|
||||
]
|
||||
}
|
||||
electron.remote.dialog.showSaveDialog(electron.remote.getCurrentWindow(), opts, function (savePath) {
|
||||
var torrentPath = TorrentSummary.getTorrentPath(torrentSummary)
|
||||
fs.readFile(torrentPath, function (err, torrentFile) {
|
||||
if (err) return onError(err)
|
||||
fs.writeFile(savePath, torrentFile, function (err) {
|
||||
if (err) return onError(err)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -1268,7 +1032,35 @@ function showOrHidePlayerControls () {
|
||||
return false
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
// 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) {
|
||||
if (!Array.isArray(files)) files = [ files ]
|
||||
|
||||
if (state.modal) {
|
||||
state.modal = null
|
||||
}
|
||||
|
||||
var subtitles = files.filter(isSubtitle)
|
||||
|
||||
if (state.location.url() === 'home' || subtitles.length === 0) {
|
||||
if (files.every(isTorrent)) {
|
||||
if (state.location.url() !== 'home') {
|
||||
backToList()
|
||||
}
|
||||
// All .torrent files? Add them.
|
||||
files.forEach((file) => controllers.torrentList.addTorrent(file))
|
||||
} else {
|
||||
// Show the Create Torrent screen. Let's seed those files.
|
||||
controllers.torrentList.showCreateTorrent(files)
|
||||
}
|
||||
} else if (state.location.url() === 'player') {
|
||||
addSubtitles(subtitles, true)
|
||||
}
|
||||
|
||||
update()
|
||||
}
|
||||
|
||||
function onError (err) {
|
||||
console.error(err.stack || err)
|
||||
sound.play('ERROR')
|
||||
@@ -1276,6 +1068,7 @@ function onError (err) {
|
||||
time: new Date().getTime(),
|
||||
message: err.message || err
|
||||
})
|
||||
|
||||
update()
|
||||
}
|
||||
|
||||
@@ -1290,7 +1083,7 @@ function onPaste (e) {
|
||||
torrentIds.forEach(function (torrentId) {
|
||||
torrentId = torrentId.trim()
|
||||
if (torrentId.length === 0) return
|
||||
addTorrent(torrentId)
|
||||
controllers.torrentList.addTorrent(torrentId)
|
||||
})
|
||||
|
||||
update()
|
||||
@@ -1310,3 +1103,13 @@ function onBlur () {
|
||||
function onVisibilityChange () {
|
||||
state.window.isVisible = !document.webkitHidden
|
||||
}
|
||||
|
||||
function onFullscreenChanged (e, isFullScreen) {
|
||||
state.window.isFullScreen = isFullScreen
|
||||
if (!isFullScreen) {
|
||||
// Aspect ratio gets reset in fullscreen mode, so restore it (OS X)
|
||||
ipcRenderer.send('setAspectRatio', state.playing.aspectRatio)
|
||||
}
|
||||
|
||||
update()
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ var path = require('path')
|
||||
var crashReporter = require('../crash-reporter')
|
||||
var config = require('../config')
|
||||
var torrentPoster = require('./lib/torrent-poster')
|
||||
var TorrentSummary = require('./lib/torrent-summary')
|
||||
|
||||
// Report when the process crashes
|
||||
crashReporter.init()
|
||||
@@ -42,8 +43,8 @@ function init () {
|
||||
client.on('warning', (err) => ipc.send('wt-warning', null, err.message))
|
||||
client.on('error', (err) => ipc.send('wt-error', null, err.message))
|
||||
|
||||
ipc.on('wt-start-torrenting', (e, torrentKey, torrentID, path, fileModtimes, selections) =>
|
||||
startTorrenting(torrentKey, torrentID, path, fileModtimes, selections))
|
||||
ipc.on('wt-start-torrenting', (e, torrentSummary) =>
|
||||
startTorrenting(torrentSummary))
|
||||
ipc.on('wt-stop-torrenting', (e, infoHash) =>
|
||||
stopTorrenting(infoHash))
|
||||
ipc.on('wt-create-torrent', (e, torrentKey, options) =>
|
||||
@@ -72,12 +73,15 @@ function init () {
|
||||
|
||||
// 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 startTorrenting (torrentKey, torrentID, path, fileModtimes, selections) {
|
||||
function startTorrenting (torrentSummary) {
|
||||
var s = torrentSummary
|
||||
var torrentKey = s.torrentKey
|
||||
var torrentID = TorrentSummary.getTorrentID(s)
|
||||
console.log('starting torrent %s: %s', torrentKey, torrentID)
|
||||
|
||||
var torrent = client.add(torrentID, {
|
||||
path: path,
|
||||
fileModtimes: fileModtimes
|
||||
path: s.path,
|
||||
fileModtimes: s.fileModtimes
|
||||
})
|
||||
torrent.key = torrentKey
|
||||
|
||||
@@ -85,7 +89,7 @@ function startTorrenting (torrentKey, torrentID, path, fileModtimes, selections)
|
||||
addTorrentEvents(torrent)
|
||||
|
||||
// Only download the files the user wants, not necessarily all files
|
||||
torrent.once('ready', () => selectFiles(torrent, selections))
|
||||
torrent.once('ready', () => selectFiles(torrent, s.selections))
|
||||
|
||||
return torrent
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user