@@ -24,7 +24,8 @@
|
|||||||
"pretty-bytes": "^3.0.0",
|
"pretty-bytes": "^3.0.0",
|
||||||
"upload-element": "^1.0.1",
|
"upload-element": "^1.0.1",
|
||||||
"virtual-dom": "^2.1.1",
|
"virtual-dom": "^2.1.1",
|
||||||
"webtorrent": "^0.82.1"
|
"webtorrent": "^0.82.1",
|
||||||
|
"xtend": "^4.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"electron-packager": "^5.0.0",
|
"electron-packager": "^5.0.0",
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ var EventEmitter = require('events')
|
|||||||
var mainLoop = require('main-loop')
|
var mainLoop = require('main-loop')
|
||||||
var networkAddress = require('network-address')
|
var networkAddress = require('network-address')
|
||||||
var path = require('path')
|
var path = require('path')
|
||||||
var state = require('./state')
|
|
||||||
var torrentPoster = require('./lib/torrent-poster')
|
var torrentPoster = require('./lib/torrent-poster')
|
||||||
var WebTorrent = require('webtorrent')
|
var WebTorrent = require('webtorrent')
|
||||||
var cfg = require('application-config')('WebTorrent')
|
var cfg = require('application-config')('WebTorrent')
|
||||||
|
var extend = require('xtend')
|
||||||
|
|
||||||
var createElement = require('virtual-dom/create-element')
|
var createElement = require('virtual-dom/create-element')
|
||||||
var diff = require('virtual-dom/diff')
|
var diff = require('virtual-dom/diff')
|
||||||
@@ -20,6 +20,9 @@ var patch = require('virtual-dom/patch')
|
|||||||
|
|
||||||
var App = require('./views/app')
|
var App = require('./views/app')
|
||||||
|
|
||||||
|
// For easy debugging in Developer Tools
|
||||||
|
var state = global.state = require('./state')
|
||||||
|
|
||||||
// Force use of webtorrent trackers on all torrents
|
// Force use of webtorrent trackers on all torrents
|
||||||
global.WEBTORRENT_ANNOUNCE = createTorrent.announceList
|
global.WEBTORRENT_ANNOUNCE = createTorrent.announceList
|
||||||
.map(function (arr) {
|
.map(function (arr) {
|
||||||
@@ -30,6 +33,15 @@ global.WEBTORRENT_ANNOUNCE = createTorrent.announceList
|
|||||||
})
|
})
|
||||||
|
|
||||||
var vdomLoop
|
var vdomLoop
|
||||||
|
var HOME = process.env.HOME || process.env.USERPROFILE
|
||||||
|
var defaultSaved = {
|
||||||
|
torrents: [],
|
||||||
|
downloads: path.join(HOME, 'Downloads')
|
||||||
|
}
|
||||||
|
|
||||||
|
// All state lives in state.js. `state.saved` is read from and written to a file.
|
||||||
|
// All other state is ephemeral. First we load state.saved then initialize the app.
|
||||||
|
loadState(init)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called once when the application loads. (Not once per window.)
|
* Called once when the application loads. (Not once per window.)
|
||||||
@@ -42,6 +54,7 @@ function init () {
|
|||||||
state.client = new WebTorrent()
|
state.client = new WebTorrent()
|
||||||
state.client.on('warning', onWarning)
|
state.client.on('warning', onWarning)
|
||||||
state.client.on('error', onError)
|
state.client.on('error', onError)
|
||||||
|
state.client.on('torrent', saveTorrentData)
|
||||||
|
|
||||||
// The UI is built with virtual-dom, a minimalist library extracted from React
|
// 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
|
// The concepts--one way data flow, a pure function that renders state to a
|
||||||
@@ -59,13 +72,12 @@ function init () {
|
|||||||
// (eg % downloaded) and to keep the cursor in sync when playing a video
|
// (eg % downloaded) and to keep the cursor in sync when playing a video
|
||||||
setInterval(update, 1000)
|
setInterval(update, 1000)
|
||||||
|
|
||||||
// All state lives in state.js. `state.saved` is read from and written to a
|
// Resume all saved torrents now that state is loaded and vdom is ready
|
||||||
// file. All other state is ephemeral. Here we'll load state.saved:
|
resumeAllTorrents()
|
||||||
loadState()
|
window.addEventListener('beforeunload', saveState)
|
||||||
document.addEventListener('unload', saveState)
|
|
||||||
|
|
||||||
// For easy debugging in Developer Tools
|
// listen for messages from the main process
|
||||||
global.state = state
|
setupIpc()
|
||||||
|
|
||||||
// OS integrations:
|
// OS integrations:
|
||||||
// ...Chromecast and Airplay
|
// ...Chromecast and Airplay
|
||||||
@@ -110,7 +122,6 @@ function init () {
|
|||||||
state.isFocused = false
|
state.isFocused = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
init()
|
|
||||||
|
|
||||||
// This is the (mostly) pure funtion from state -> UI. Returns a virtual DOM
|
// This is the (mostly) pure funtion from state -> UI. Returns a virtual DOM
|
||||||
// tree. Any events, such as button clicks, will turn into calls to dispatch()
|
// tree. Any events, such as button clicks, will turn into calls to dispatch()
|
||||||
@@ -180,37 +191,45 @@ function dispatch (action, ...args) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
electron.ipcRenderer.on('addTorrent', function (e, torrentId) {
|
function setupIpc () {
|
||||||
dispatch('addTorrent', torrentId)
|
electron.ipcRenderer.on('addTorrent', function (e, torrentId) {
|
||||||
})
|
dispatch('addTorrent', torrentId)
|
||||||
|
})
|
||||||
|
|
||||||
electron.ipcRenderer.on('seed', function (e, files) {
|
electron.ipcRenderer.on('seed', function (e, files) {
|
||||||
dispatch('seed', files)
|
dispatch('seed', files)
|
||||||
})
|
})
|
||||||
|
|
||||||
electron.ipcRenderer.on('fullscreenChanged', function (e, isFullScreen) {
|
electron.ipcRenderer.on('fullscreenChanged', function (e, isFullScreen) {
|
||||||
state.isFullScreen = isFullScreen
|
state.isFullScreen = isFullScreen
|
||||||
update()
|
update()
|
||||||
})
|
})
|
||||||
|
|
||||||
electron.ipcRenderer.on('addFakeDevice', function (e, device) {
|
electron.ipcRenderer.on('addFakeDevice', function (e, device) {
|
||||||
var player = new EventEmitter()
|
var player = new EventEmitter()
|
||||||
player.play = (networkURL) => console.log(networkURL)
|
player.play = (networkURL) => console.log(networkURL)
|
||||||
state.devices[device] = player
|
state.devices[device] = player
|
||||||
update()
|
update()
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Load state.saved from the JSON state file
|
// Load state.saved from the JSON state file
|
||||||
function loadState () {
|
function loadState (callback) {
|
||||||
cfg.read(function (err, data) {
|
cfg.read(function (err, data) {
|
||||||
if (err) console.error(err)
|
if (err) console.error(err)
|
||||||
electron.ipcRenderer.send('log', 'loaded state from ' + cfg.filePath)
|
electron.ipcRenderer.send('log', 'loaded state from ' + cfg.filePath)
|
||||||
state.saved = data
|
|
||||||
if (!state.saved.torrents) state.saved.torrents = []
|
// populate defaults if they're not there
|
||||||
state.saved.torrents.forEach((x) => startTorrenting(x.infoHash))
|
state.saved = extend(defaultSaved, data)
|
||||||
|
|
||||||
|
if (callback) callback()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resumeAllTorrents () {
|
||||||
|
state.saved.torrents.forEach((x) => startTorrenting(x.infoHash))
|
||||||
|
}
|
||||||
|
|
||||||
// Write state.saved to the JSON state file
|
// Write state.saved to the JSON state file
|
||||||
function saveState () {
|
function saveState () {
|
||||||
electron.ipcRenderer.send('log', 'saving state to ' + cfg.filePath)
|
electron.ipcRenderer.send('log', 'saving state to ' + cfg.filePath)
|
||||||
@@ -259,10 +278,27 @@ function isNotTorrentFile (file) {
|
|||||||
function addTorrent (torrentId) {
|
function addTorrent (torrentId) {
|
||||||
if (!torrentId) torrentId = 'magnet:?xt=urn:btih:6a9759bffd5c0af65319979fb7832189f4f3c35d&dn=sintel.mp4'
|
if (!torrentId) torrentId = 'magnet:?xt=urn:btih:6a9759bffd5c0af65319979fb7832189f4f3c35d&dn=sintel.mp4'
|
||||||
var torrent = startTorrenting(torrentId)
|
var torrent = startTorrenting(torrentId)
|
||||||
if (state.saved.torrents.find((x) => x.infoHash === torrent.infoHash)) {
|
|
||||||
return // torrent is already in state.saved
|
// check if torrent is duplicate
|
||||||
|
var exists = state.saved.torrents.find((x) => x.infoHash === torrent.infoHash)
|
||||||
|
if (exists) return window.alert('That torrent is already downloading.')
|
||||||
|
|
||||||
|
// save only if infoHash is available
|
||||||
|
if (torrent.infoHash) {
|
||||||
|
state.saved.torrents.push({
|
||||||
|
infoHash: torrent.infoHash
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
torrent.on('infoHash', () => saveTorrentData(torrent))
|
||||||
}
|
}
|
||||||
state.saved.torrents.push({
|
|
||||||
|
saveState()
|
||||||
|
}
|
||||||
|
|
||||||
|
// add torrent metadata to state once it's available
|
||||||
|
function saveTorrentData (torrent) {
|
||||||
|
var ix = state.saved.torrents.findIndex((x) => x.infoHash === torrent.infoHash)
|
||||||
|
var data = {
|
||||||
name: torrent.name,
|
name: torrent.name,
|
||||||
magnetURI: torrent.magnetURI,
|
magnetURI: torrent.magnetURI,
|
||||||
infoHash: torrent.infoHash,
|
infoHash: torrent.infoHash,
|
||||||
@@ -270,13 +306,20 @@ function addTorrent (torrentId) {
|
|||||||
xt: torrent.xt,
|
xt: torrent.xt,
|
||||||
dn: torrent.dn,
|
dn: torrent.dn,
|
||||||
announce: torrent.announce
|
announce: torrent.announce
|
||||||
})
|
}
|
||||||
|
|
||||||
|
if (ix === -1) state.saved.torrents.push(data)
|
||||||
|
else state.saved.torrents[ix] = data
|
||||||
|
|
||||||
saveState()
|
saveState()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Starts downloading and/or seeding a given torrent file or magnet URI
|
// Starts downloading and/or seeding a given torrent file or magnet URI
|
||||||
function startTorrenting (torrentId) {
|
function startTorrenting (torrentId) {
|
||||||
var torrent = state.client.add(torrentId)
|
var torrent = state.client.add(torrentId, {
|
||||||
|
// use downloads folder
|
||||||
|
path: state.saved.downloads
|
||||||
|
})
|
||||||
addTorrentEvents(torrent)
|
addTorrentEvents(torrent)
|
||||||
return torrent
|
return torrent
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user