From 4e07ecf05ca119bef9febe4a6c2230432b456d96 Mon Sep 17 00:00:00 2001 From: Nate Goldman Date: Sun, 6 Mar 2016 23:06:30 -0800 Subject: [PATCH 1/3] load state first --- renderer/index.js | 60 +++++++++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/renderer/index.js b/renderer/index.js index 873280ba..a234d034 100644 --- a/renderer/index.js +++ b/renderer/index.js @@ -9,7 +9,6 @@ var EventEmitter = require('events') var mainLoop = require('main-loop') var networkAddress = require('network-address') var path = require('path') -var state = require('./state') var torrentPoster = require('./lib/torrent-poster') var WebTorrent = require('webtorrent') var cfg = require('application-config')('WebTorrent') @@ -20,6 +19,9 @@ var patch = require('virtual-dom/patch') 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 global.WEBTORRENT_ANNOUNCE = createTorrent.announceList .map(function (arr) { @@ -31,6 +33,10 @@ global.WEBTORRENT_ANNOUNCE = createTorrent.announceList var vdomLoop +// 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.) * Connects to the torrent networks, sets up the UI and OS integrations like @@ -59,13 +65,12 @@ function init () { // (eg % downloaded) and to keep the cursor in sync when playing a video setInterval(update, 1000) - // All state lives in state.js. `state.saved` is read from and written to a - // file. All other state is ephemeral. Here we'll load state.saved: - loadState() + // Resume all saved torrents now that state is loaded and vdom is ready + resumeAllTorrents() document.addEventListener('unload', saveState) - // For easy debugging in Developer Tools - global.state = state + // listen for messages from the main process + setupIpc() // OS integrations: // ...Chromecast and Airplay @@ -108,7 +113,6 @@ function init () { state.isFocused = false }) } -init() // 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() @@ -178,37 +182,43 @@ function dispatch (action, ...args) { } } -electron.ipcRenderer.on('addTorrent', function (e, torrentId) { - dispatch('addTorrent', torrentId) -}) +function setupIpc () { + electron.ipcRenderer.on('addTorrent', function (e, torrentId) { + dispatch('addTorrent', torrentId) + }) -electron.ipcRenderer.on('seed', function (e, files) { - dispatch('seed', files) -}) + electron.ipcRenderer.on('seed', function (e, files) { + dispatch('seed', files) + }) -electron.ipcRenderer.on('fullscreenChanged', function (e, isFullScreen) { - state.isFullScreen = isFullScreen - update() -}) + electron.ipcRenderer.on('fullscreenChanged', function (e, isFullScreen) { + state.isFullScreen = isFullScreen + update() + }) -electron.ipcRenderer.on('addFakeDevice', function (e, device) { - var player = new EventEmitter() - player.play = (networkURL) => console.log(networkURL) - state.devices[device] = player - update() -}) + electron.ipcRenderer.on('addFakeDevice', function (e, device) { + var player = new EventEmitter() + player.play = (networkURL) => console.log(networkURL) + state.devices[device] = player + update() + }) +} // Load state.saved from the JSON state file -function loadState () { +function loadState (callback) { cfg.read(function (err, data) { if (err) console.error(err) electron.ipcRenderer.send('log', 'loaded state from ' + cfg.filePath) state.saved = data if (!state.saved.torrents) state.saved.torrents = [] - state.saved.torrents.forEach((x) => startTorrenting(x.infoHash)) + if (callback) callback() }) } +function resumeAllTorrents () { + state.saved.torrents.forEach((x) => startTorrenting(x.infoHash)) +} + // Write state.saved to the JSON state file function saveState () { electron.ipcRenderer.send('log', 'saving state to ' + cfg.filePath) From 383659ad1ac04df6d0e7f32cf611132bc37d306b Mon Sep 17 00:00:00 2001 From: Nate Goldman Date: Mon, 7 Mar 2016 00:03:26 -0800 Subject: [PATCH 2/3] add downloads folder - fix #53 - fixed issue where nothing but infoHash was getting saved - better defaults mgmt for state.saved - no duplicates saved --- package.json | 3 ++- renderer/index.js | 43 ++++++++++++++++++++++++++++++++++++------- 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 339eaeac..447e8397 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,8 @@ "pretty-bytes": "^3.0.0", "upload-element": "^1.0.1", "virtual-dom": "^2.1.1", - "webtorrent": "^0.82.1" + "webtorrent": "^0.82.1", + "xtend": "^4.0.1" }, "devDependencies": { "electron-packager": "^5.0.0", diff --git a/renderer/index.js b/renderer/index.js index a234d034..99052238 100644 --- a/renderer/index.js +++ b/renderer/index.js @@ -12,6 +12,7 @@ var path = require('path') var torrentPoster = require('./lib/torrent-poster') var WebTorrent = require('webtorrent') var cfg = require('application-config')('WebTorrent') +var extend = require('xtend') var createElement = require('virtual-dom/create-element') var diff = require('virtual-dom/diff') @@ -32,6 +33,11 @@ global.WEBTORRENT_ANNOUNCE = createTorrent.announceList }) 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. @@ -48,6 +54,7 @@ function init () { state.client = new WebTorrent() state.client.on('warning', onWarning) state.client.on('error', onError) + state.client.on('torrent', updateTorrentData) // 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 @@ -209,8 +216,10 @@ function loadState (callback) { cfg.read(function (err, data) { if (err) console.error(err) 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 = extend(defaultSaved, data) + if (callback) callback() }) } @@ -267,10 +276,26 @@ function isNotTorrentFile (file) { function addTorrent (torrentId) { if (!torrentId) torrentId = 'magnet:?xt=urn:btih:6a9759bffd5c0af65319979fb7832189f4f3c35d&dn=sintel.mp4' 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.') + + // only infoHash is available until torrent is ready state.saved.torrents.push({ + infoHash: torrent.infoHash + }) + saveState() +} + +// add torrent metadata to state once it's available +function updateTorrentData (torrent) { + // get torrent index + var i = state.saved.torrents.findIndex((x) => x.infoHash === torrent.infoHash) + if (i === -1) return + + // add the goods + state.saved.torrents[i] = { name: torrent.name, magnetURI: torrent.magnetURI, infoHash: torrent.infoHash, @@ -278,13 +303,17 @@ function addTorrent (torrentId) { xt: torrent.xt, dn: torrent.dn, announce: torrent.announce - }) + } + saveState() } // Starts downloading and/or seeding a given torrent file or magnet URI function startTorrenting (torrentId) { - var torrent = state.client.add(torrentId) + var torrent = state.client.add(torrentId, { + // use downloads folder + path: state.saved.downloads + }) addTorrentEvents(torrent) return torrent } From de7bc442a741917c7697f904b2f79c9985bcc70c Mon Sep 17 00:00:00 2001 From: Nate Goldman Date: Mon, 7 Mar 2016 10:51:32 -0800 Subject: [PATCH 3/3] fix empty infoHash issue --- renderer/index.js | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/renderer/index.js b/renderer/index.js index 99052238..567ee48f 100644 --- a/renderer/index.js +++ b/renderer/index.js @@ -54,7 +54,7 @@ function init () { state.client = new WebTorrent() state.client.on('warning', onWarning) state.client.on('error', onError) - state.client.on('torrent', updateTorrentData) + state.client.on('torrent', saveTorrentData) // 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 @@ -74,7 +74,7 @@ function init () { // Resume all saved torrents now that state is loaded and vdom is ready resumeAllTorrents() - document.addEventListener('unload', saveState) + window.addEventListener('beforeunload', saveState) // listen for messages from the main process setupIpc() @@ -281,21 +281,22 @@ function addTorrent (torrentId) { var exists = state.saved.torrents.find((x) => x.infoHash === torrent.infoHash) if (exists) return window.alert('That torrent is already downloading.') - // only infoHash is available until torrent is ready - state.saved.torrents.push({ - infoHash: torrent.infoHash - }) + // save only if infoHash is available + if (torrent.infoHash) { + state.saved.torrents.push({ + infoHash: torrent.infoHash + }) + } else { + torrent.on('infoHash', () => saveTorrentData(torrent)) + } + saveState() } // add torrent metadata to state once it's available -function updateTorrentData (torrent) { - // get torrent index - var i = state.saved.torrents.findIndex((x) => x.infoHash === torrent.infoHash) - if (i === -1) return - - // add the goods - state.saved.torrents[i] = { +function saveTorrentData (torrent) { + var ix = state.saved.torrents.findIndex((x) => x.infoHash === torrent.infoHash) + var data = { name: torrent.name, magnetURI: torrent.magnetURI, infoHash: torrent.infoHash, @@ -305,6 +306,9 @@ function updateTorrentData (torrent) { announce: torrent.announce } + if (ix === -1) state.saved.torrents.push(data) + else state.saved.torrents[ix] = data + saveState() }