diff --git a/package.json b/package.json index 10939f8b..5246638f 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "capture-frame": "^1.0.0", "chromecasts": "^1.8.0", "create-torrent": "^3.24.5", + "debounce": "^1.0.0", "deep-equal": "^1.0.1", "dlnacasts": "^0.1.0", "drag-drop": "^2.12.1", diff --git a/src/main/index.js b/src/main/index.js index cadbb766..a1e5a3dc 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -107,12 +107,12 @@ function init () { app.isQuitting = true e.preventDefault() - windows.main.dispatch('saveState') // try to save state on exit - ipcMain.once('savedState', () => app.quit()) + windows.main.dispatch('stateSaveImmediate') // try to save state on exit + ipcMain.once('stateSaved', () => app.quit()) setTimeout(() => { console.error('Saving state took too long. Quitting.') app.quit() - }, 2000) // quit after 2 secs, at most + }, 4000) // quit after 4 secs, at most }) app.on('activate', function () { diff --git a/src/renderer/controllers/prefs-controller.js b/src/renderer/controllers/prefs-controller.js index b85578f4..d2773f1a 100644 --- a/src/renderer/controllers/prefs-controller.js +++ b/src/renderer/controllers/prefs-controller.js @@ -1,4 +1,3 @@ -const State = require('../lib/state') const {dispatch} = require('../lib/dispatcher') const ipcRenderer = require('electron').ipcRenderer @@ -56,7 +55,7 @@ module.exports = class PrefsController { ipcRenderer.send('setStartup', state.unsaved.prefs.startup) } state.saved.prefs = Object.assign(state.saved.prefs || {}, state.unsaved.prefs) - State.save(state) + dispatch('stateSaveImmediate') dispatch('checkDownloadPath') } } diff --git a/src/renderer/controllers/torrent-controller.js b/src/renderer/controllers/torrent-controller.js index 7e4ee824..73b6757d 100644 --- a/src/renderer/controllers/torrent-controller.js +++ b/src/renderer/controllers/torrent-controller.js @@ -129,20 +129,20 @@ module.exports = class TorrentController { const torrentSummary = this.getTorrentSummary(torrentKey) if (!torrentSummary) throw new Error('Not saving modtimes for deleted torrent ' + torrentKey) torrentSummary.fileModtimes = fileModtimes - dispatch('saveStateThrottled') + dispatch('stateSave') } torrentFileSaved (torrentKey, torrentFileName) { console.log('torrent file saved %s: %s', torrentKey, torrentFileName) const torrentSummary = this.getTorrentSummary(torrentKey) torrentSummary.torrentFileName = torrentFileName - dispatch('saveStateThrottled') + dispatch('stateSave') } torrentPosterSaved (torrentKey, posterFileName) { const torrentSummary = this.getTorrentSummary(torrentKey) torrentSummary.posterFileName = posterFileName - dispatch('saveStateThrottled') + dispatch('stateSave') } torrentAudioMetadata (infoHash, index, info) { diff --git a/src/renderer/controllers/torrent-list-controller.js b/src/renderer/controllers/torrent-list-controller.js index 8bfc2b48..1bb8ac71 100644 --- a/src/renderer/controllers/torrent-list-controller.js +++ b/src/renderer/controllers/torrent-list-controller.js @@ -4,7 +4,6 @@ const electron = require('electron') const {dispatch} = require('../lib/dispatcher') const {TorrentKeyNotFoundError} = require('../lib/errors') -const State = require('../lib/state') const sound = require('../lib/sound') const TorrentSummary = require('../lib/torrent-summary') @@ -159,7 +158,7 @@ module.exports = class TorrentListController { // remove torrent from saved list this.state.saved.torrents.splice(index, 1) - State.saveThrottled(this.state) + dispatch('stateSave') } // prevent user from going forward to a deleted torrent diff --git a/src/renderer/controllers/update-controller.js b/src/renderer/controllers/update-controller.js index a5fd3d74..f7773edc 100644 --- a/src/renderer/controllers/update-controller.js +++ b/src/renderer/controllers/update-controller.js @@ -1,4 +1,4 @@ -const State = require('../lib/state') +const {dispatch} = require('../lib/dispatcher') // Controls the UI checking for new versions of the app, prompting install module.exports = class UpdateController { @@ -21,6 +21,6 @@ module.exports = class UpdateController { let skipped = this.state.saved.skippedVersions if (!skipped) skipped = this.state.saved.skippedVersions = [] skipped.push(version) - State.saveThrottled(this.state) + dispatch('stateSave') } } diff --git a/src/renderer/lib/state.js b/src/renderer/lib/state.js index 2ea67df1..6e37ff81 100644 --- a/src/renderer/lib/state.js +++ b/src/renderer/lib/state.js @@ -1,15 +1,19 @@ const appConfig = require('application-config')('WebTorrent') +const debounce = require('debounce') const path = require('path') const {EventEmitter} = require('events') const config = require('../../config') const migrations = require('./migrations') +const SAVE_DEBOUNCE_INTERVAL = 1000 + const State = module.exports = Object.assign(new EventEmitter(), { getDefaultPlayState, load, - save, - saveThrottled + // state.save() calls are rate-limited. Use state.saveImmediate() to skip limit. + save: debounce(saveImmediate, SAVE_DEBOUNCE_INTERVAL), + saveImmediate }) appConfig.filePath = path.join(config.CONFIG_PATH, 'config.json') @@ -98,7 +102,7 @@ function getDefaultPlayState () { } /* If the saved state file doesn't exist yet, here's what we use instead */ -function setupSavedState (cb) { +function setupStateSaved (cb) { const fs = require('fs-extra') const parseTorrent = require('parse-torrent') const parallel = require('run-parallel') @@ -182,7 +186,7 @@ function load (cb) { appConfig.read(function (err, saved) { if (err || !saved.version) { console.log('Missing config file: Creating new one') - setupSavedState(onSaved) + setupStateSaved(onSaved) } else { onSaved(null, saved) } @@ -197,9 +201,8 @@ function load (cb) { } // Write state.saved to the JSON state file -function save (state, cb) { +function saveImmediate (state, cb) { console.log('Saving state to ' + appConfig.filePath) - delete state.saveStateTimeout // Clean up, so that we're not saving any pending state const copy = Object.assign({}, state.saved) @@ -226,15 +229,6 @@ function save (state, cb) { appConfig.write(copy, (err) => { if (err) console.error(err) - else State.emit('savedState') + else State.emit('stateSaved') }) } - -// Write, but no more than once a second -function saveThrottled (state) { - if (state.saveStateTimeout) return - state.saveStateTimeout = setTimeout(function () { - if (!state.saveStateTimeout) return - save(state) - }, 1000) -} diff --git a/src/renderer/main.js b/src/renderer/main.js index d0104e31..4407abb9 100644 --- a/src/renderer/main.js +++ b/src/renderer/main.js @@ -257,8 +257,8 @@ const dispatchHandlers = { 'onOpen': onOpen, 'error': onError, 'uncaughtError': (proc, err) => telemetry.logUncaughtError(proc, err), - 'saveState': () => State.save(state), - 'saveStateThrottled': () => State.saveThrottled(state), + 'stateSave': () => State.save(state), + 'stateSaveImmediate': () => State.saveImmediate(state), 'update': () => {} // No-op, just trigger an update } @@ -308,7 +308,7 @@ function setupIpc () { ipcRenderer.send('ipcReady') - State.on('savedState', () => ipcRenderer.send('savedState')) + State.on('stateSaved', () => ipcRenderer.send('stateSaved')) } // Quits any modal popovers and returns to the torrent list screen @@ -465,7 +465,7 @@ function onFullscreenChanged (e, isFullScreen) { function onWindowBoundsChanged (e, newBounds) { state.saved.bounds = newBounds - dispatch('saveStateThrottled') + dispatch('stateSave') } function checkDownloadPath () {