Merge pull request #59 from feross/load-state

load state first
This commit is contained in:
Feross Aboukhadijeh
2016-03-07 11:18:11 -08:00
2 changed files with 78 additions and 34 deletions

View File

@@ -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",

View File

@@ -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
} }