mirror of
https://github.com/atom/atom.git
synced 2026-01-24 22:38:20 -05:00
Merge pull request #10675 from atom/dh-expose-updates
Expose application updater lifecycle events to packages
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -15,3 +15,4 @@ debug.log
|
||||
docs/output
|
||||
docs/includes
|
||||
spec/fixtures/evil-files/
|
||||
out/
|
||||
|
||||
@@ -328,3 +328,18 @@ describe "AtomEnvironment", ->
|
||||
runs ->
|
||||
{releaseVersion} = updateAvailableHandler.mostRecentCall.args[0]
|
||||
expect(releaseVersion).toBe 'version'
|
||||
|
||||
describe "::getReleaseChannel()", ->
|
||||
[version] = []
|
||||
beforeEach ->
|
||||
spyOn(atom, 'getVersion').andCallFake -> version
|
||||
|
||||
it "returns the correct channel based on the version number", ->
|
||||
version = '1.5.6'
|
||||
expect(atom.getReleaseChannel()).toBe 'stable'
|
||||
|
||||
version = '1.5.0-beta10'
|
||||
expect(atom.getReleaseChannel()).toBe 'beta'
|
||||
|
||||
version = '1.7.0-dev-5340c91'
|
||||
expect(atom.getReleaseChannel()).toBe 'dev'
|
||||
|
||||
115
spec/auto-update-manager-spec.js
Normal file
115
spec/auto-update-manager-spec.js
Normal file
@@ -0,0 +1,115 @@
|
||||
'use babel'
|
||||
|
||||
import AutoUpdateManager from '../src/auto-update-manager'
|
||||
import {remote} from 'electron'
|
||||
const electronAutoUpdater = remote.require('electron').autoUpdater
|
||||
|
||||
describe('AutoUpdateManager (renderer)', () => {
|
||||
let autoUpdateManager
|
||||
|
||||
beforeEach(() => {
|
||||
autoUpdateManager = new AutoUpdateManager({
|
||||
applicationDelegate: atom.applicationDelegate
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
autoUpdateManager.destroy()
|
||||
})
|
||||
|
||||
describe('::onDidBeginCheckingForUpdate', () => {
|
||||
it('subscribes to "did-begin-checking-for-update" event', () => {
|
||||
const spy = jasmine.createSpy('spy')
|
||||
autoUpdateManager.onDidBeginCheckingForUpdate(spy)
|
||||
electronAutoUpdater.emit('checking-for-update')
|
||||
waitsFor(() => {
|
||||
return spy.callCount === 1
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('::onDidBeginDownloadingUpdate', () => {
|
||||
it('subscribes to "did-begin-downloading-update" event', () => {
|
||||
const spy = jasmine.createSpy('spy')
|
||||
autoUpdateManager.onDidBeginDownloadingUpdate(spy)
|
||||
electronAutoUpdater.emit('update-available')
|
||||
waitsFor(() => {
|
||||
return spy.callCount === 1
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('::onDidCompleteDownloadingUpdate', () => {
|
||||
it('subscribes to "did-complete-downloading-update" event', () => {
|
||||
const spy = jasmine.createSpy('spy')
|
||||
autoUpdateManager.onDidCompleteDownloadingUpdate(spy)
|
||||
electronAutoUpdater.emit('update-downloaded', null, null, '1.2.3')
|
||||
waitsFor(() => {
|
||||
return spy.callCount === 1
|
||||
})
|
||||
runs(() => {
|
||||
expect(spy.mostRecentCall.args[0].releaseVersion).toBe('1.2.3')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('::onUpdateNotAvailable', () => {
|
||||
it('subscribes to "update-not-available" event', () => {
|
||||
const spy = jasmine.createSpy('spy')
|
||||
autoUpdateManager.onUpdateNotAvailable(spy)
|
||||
electronAutoUpdater.emit('update-not-available')
|
||||
waitsFor(() => {
|
||||
return spy.callCount === 1
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('::platformSupportsUpdates', () => {
|
||||
let state, releaseChannel
|
||||
it('returns true on OS X and Windows when in stable', () => {
|
||||
spyOn(autoUpdateManager, 'getState').andCallFake(() => state)
|
||||
spyOn(atom, 'getReleaseChannel').andCallFake(() => releaseChannel)
|
||||
|
||||
state = 'idle'
|
||||
releaseChannel = 'stable'
|
||||
expect(autoUpdateManager.platformSupportsUpdates()).toBe(true)
|
||||
|
||||
state = 'idle'
|
||||
releaseChannel = 'dev'
|
||||
expect(autoUpdateManager.platformSupportsUpdates()).toBe(false)
|
||||
|
||||
state = 'unsupported'
|
||||
releaseChannel = 'stable'
|
||||
expect(autoUpdateManager.platformSupportsUpdates()).toBe(false)
|
||||
|
||||
state = 'unsupported'
|
||||
releaseChannel = 'dev'
|
||||
expect(autoUpdateManager.platformSupportsUpdates()).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('::destroy', () => {
|
||||
it('unsubscribes from all events', () => {
|
||||
const spy = jasmine.createSpy('spy')
|
||||
const doneIndicator = jasmine.createSpy('spy')
|
||||
atom.applicationDelegate.onUpdateNotAvailable(doneIndicator)
|
||||
autoUpdateManager.onDidBeginCheckingForUpdate(spy)
|
||||
autoUpdateManager.onDidBeginDownloadingUpdate(spy)
|
||||
autoUpdateManager.onDidCompleteDownloadingUpdate(spy)
|
||||
autoUpdateManager.onUpdateNotAvailable(spy)
|
||||
autoUpdateManager.destroy()
|
||||
electronAutoUpdater.emit('checking-for-update')
|
||||
electronAutoUpdater.emit('update-available')
|
||||
electronAutoUpdater.emit('update-downloaded', null, null, '1.2.3')
|
||||
electronAutoUpdater.emit('update-not-available')
|
||||
|
||||
waitsFor(() => {
|
||||
return doneIndicator.callCount === 1
|
||||
})
|
||||
|
||||
runs(() => {
|
||||
expect(spy.callCount).toBe(0)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -166,8 +166,7 @@ class ApplicationDelegate
|
||||
|
||||
onDidOpenLocations: (callback) ->
|
||||
outerCallback = (event, message, detail) ->
|
||||
if message is 'open-locations'
|
||||
callback(detail)
|
||||
callback(detail) if message is 'open-locations'
|
||||
|
||||
ipcRenderer.on('message', outerCallback)
|
||||
new Disposable ->
|
||||
@@ -175,8 +174,38 @@ class ApplicationDelegate
|
||||
|
||||
onUpdateAvailable: (callback) ->
|
||||
outerCallback = (event, message, detail) ->
|
||||
if message is 'update-available'
|
||||
callback(detail)
|
||||
# TODO: Yes, this is strange that `onUpdateAvailable` is listening for
|
||||
# `did-begin-downloading-update`. We currently have no mechanism to know
|
||||
# if there is an update, so begin of downloading is a good proxy.
|
||||
callback(detail) if message is 'did-begin-downloading-update'
|
||||
|
||||
ipcRenderer.on('message', outerCallback)
|
||||
new Disposable ->
|
||||
ipcRenderer.removeListener('message', outerCallback)
|
||||
|
||||
onDidBeginDownloadingUpdate: (callback) ->
|
||||
@onUpdateAvailable(callback)
|
||||
|
||||
onDidBeginCheckingForUpdate: (callback) ->
|
||||
outerCallback = (event, message, detail) ->
|
||||
callback(detail) if message is 'checking-for-update'
|
||||
|
||||
ipcRenderer.on('message', outerCallback)
|
||||
new Disposable ->
|
||||
ipcRenderer.removeListener('message', outerCallback)
|
||||
|
||||
onDidCompleteDownloadingUpdate: (callback) ->
|
||||
outerCallback = (event, message, detail) ->
|
||||
# TODO: We could rename this event to `did-complete-downloading-update`
|
||||
callback(detail) if message is 'update-available'
|
||||
|
||||
ipcRenderer.on('message', outerCallback)
|
||||
new Disposable ->
|
||||
ipcRenderer.removeListener('message', outerCallback)
|
||||
|
||||
onUpdateNotAvailable: (callback) ->
|
||||
outerCallback = (event, message, detail) ->
|
||||
callback(detail) if message is 'update-not-available'
|
||||
|
||||
ipcRenderer.on('message', outerCallback)
|
||||
new Disposable ->
|
||||
@@ -206,3 +235,12 @@ class ApplicationDelegate
|
||||
|
||||
disablePinchToZoom: ->
|
||||
webFrame.setZoomLevelLimits(1, 1)
|
||||
|
||||
checkForUpdate: ->
|
||||
ipcRenderer.send('check-for-update')
|
||||
|
||||
restartAndInstallUpdate: ->
|
||||
ipcRenderer.send('install-update')
|
||||
|
||||
getAutoUpdateManagerState: ->
|
||||
ipcRenderer.sendSync('get-auto-update-manager-state')
|
||||
|
||||
@@ -41,6 +41,7 @@ TextEditor = require './text-editor'
|
||||
TextBuffer = require 'text-buffer'
|
||||
Gutter = require './gutter'
|
||||
TextEditorRegistry = require './text-editor-registry'
|
||||
AutoUpdateManager = require './auto-update-manager'
|
||||
|
||||
WorkspaceElement = require './workspace-element'
|
||||
PanelContainerElement = require './panel-container-element'
|
||||
@@ -115,6 +116,9 @@ class AtomEnvironment extends Model
|
||||
# Public: A {TextEditorRegistry} instance
|
||||
textEditors: null
|
||||
|
||||
# Private: An {AutoUpdateManager} instance
|
||||
autoUpdater: null
|
||||
|
||||
saveStateDebounceInterval: 1000
|
||||
|
||||
###
|
||||
@@ -188,6 +192,7 @@ class AtomEnvironment extends Model
|
||||
@themes.workspace = @workspace
|
||||
|
||||
@textEditors = new TextEditorRegistry
|
||||
@autoUpdater = new AutoUpdateManager({@applicationDelegate})
|
||||
|
||||
@config.load()
|
||||
|
||||
@@ -333,6 +338,7 @@ class AtomEnvironment extends Model
|
||||
@commands.clear()
|
||||
@stylesElement.remove()
|
||||
@config.unobserveUserConfig()
|
||||
@autoUpdater.destroy()
|
||||
|
||||
@uninstallWindowEventHandler()
|
||||
|
||||
@@ -411,6 +417,16 @@ class AtomEnvironment extends Model
|
||||
getVersion: ->
|
||||
@appVersion ?= @getLoadSettings().appVersion
|
||||
|
||||
# Returns the release channel as a {String}. Will return one of `'dev', 'beta', 'stable'`
|
||||
getReleaseChannel: ->
|
||||
version = @getVersion()
|
||||
if version.indexOf('beta') > -1
|
||||
'beta'
|
||||
else if version.indexOf('dev') > -1
|
||||
'dev'
|
||||
else
|
||||
'stable'
|
||||
|
||||
# Public: Returns a {Boolean} that is `true` if the current version is an official release.
|
||||
isReleasedVersion: ->
|
||||
not /\w{7}/.test(@getVersion()) # Check if the release is a 7-character SHA prefix
|
||||
@@ -830,7 +846,6 @@ class AtomEnvironment extends Model
|
||||
@applicationDelegate.setTemporaryWindowState(state)
|
||||
savePromise.catch(reject).then(resolve)
|
||||
|
||||
|
||||
loadState: ->
|
||||
if @enablePersistence
|
||||
if stateKey = @getStateKey(@getLoadSettings().initialPaths)
|
||||
@@ -879,6 +894,7 @@ class AtomEnvironment extends Model
|
||||
detail: error.message
|
||||
dismissable: true
|
||||
|
||||
# TODO: We should deprecate the update events here, and use `atom.autoUpdater` instead
|
||||
onUpdateAvailable: (callback) ->
|
||||
@emitter.on 'update-available', callback
|
||||
|
||||
@@ -886,7 +902,8 @@ class AtomEnvironment extends Model
|
||||
@emitter.emit 'update-available', details
|
||||
|
||||
listenForUpdates: ->
|
||||
@disposables.add(@applicationDelegate.onUpdateAvailable(@updateAvailable.bind(this)))
|
||||
# listen for updates available locally (that have been successfully downloaded)
|
||||
@disposables.add(@autoUpdater.onDidCompleteDownloadingUpdate(@updateAvailable.bind(this)))
|
||||
|
||||
setBodyPlatformClass: ->
|
||||
@document.body.classList.add("platform-#{process.platform}")
|
||||
|
||||
73
src/auto-update-manager.js
Normal file
73
src/auto-update-manager.js
Normal file
@@ -0,0 +1,73 @@
|
||||
'use babel'
|
||||
|
||||
import {Emitter, CompositeDisposable} from 'event-kit'
|
||||
|
||||
export default class AutoUpdateManager {
|
||||
constructor ({applicationDelegate}) {
|
||||
this.applicationDelegate = applicationDelegate
|
||||
this.subscriptions = new CompositeDisposable()
|
||||
this.emitter = new Emitter()
|
||||
|
||||
this.subscriptions.add(
|
||||
applicationDelegate.onDidBeginCheckingForUpdate(() => {
|
||||
this.emitter.emit('did-begin-checking-for-update')
|
||||
}),
|
||||
applicationDelegate.onDidBeginDownloadingUpdate(() => {
|
||||
this.emitter.emit('did-begin-downloading-update')
|
||||
}),
|
||||
applicationDelegate.onDidCompleteDownloadingUpdate((details) => {
|
||||
this.emitter.emit('did-complete-downloading-update', details)
|
||||
}),
|
||||
applicationDelegate.onUpdateNotAvailable(() => {
|
||||
this.emitter.emit('update-not-available')
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
destroy () {
|
||||
this.subscriptions.dispose()
|
||||
this.emitter.dispose()
|
||||
}
|
||||
|
||||
checkForUpdate () {
|
||||
this.applicationDelegate.checkForUpdate()
|
||||
}
|
||||
|
||||
restartAndInstallUpdate () {
|
||||
this.applicationDelegate.restartAndInstallUpdate()
|
||||
}
|
||||
|
||||
getState () {
|
||||
return this.applicationDelegate.getAutoUpdateManagerState()
|
||||
}
|
||||
|
||||
platformSupportsUpdates () {
|
||||
return atom.getReleaseChannel() !== 'dev' && this.getState() !== 'unsupported'
|
||||
}
|
||||
|
||||
onDidBeginCheckingForUpdate (callback) {
|
||||
return this.emitter.on('did-begin-checking-for-update', callback)
|
||||
}
|
||||
|
||||
onDidBeginDownloadingUpdate (callback) {
|
||||
return this.emitter.on('did-begin-downloading-update', callback)
|
||||
}
|
||||
|
||||
onDidCompleteDownloadingUpdate (callback) {
|
||||
return this.emitter.on('did-complete-downloading-update', callback)
|
||||
}
|
||||
|
||||
// TODO: When https://github.com/atom/electron/issues/4587 is closed, we can
|
||||
// add an update-available event.
|
||||
// onUpdateAvailable (callback) {
|
||||
// return this.emitter.on('update-available', callback)
|
||||
// }
|
||||
|
||||
onUpdateNotAvailable (callback) {
|
||||
return this.emitter.on('update-not-available', callback)
|
||||
}
|
||||
|
||||
getPlatform () {
|
||||
return process.platform
|
||||
}
|
||||
}
|
||||
@@ -304,6 +304,15 @@ class AtomApplication
|
||||
ipcMain.on 'execute-javascript-in-dev-tools', (event, code) ->
|
||||
event.sender.devToolsWebContents?.executeJavaScript(code)
|
||||
|
||||
ipcMain.on 'check-for-update', =>
|
||||
@autoUpdateManager.check()
|
||||
|
||||
ipcMain.on 'get-auto-update-manager-state', (event) =>
|
||||
event.returnValue = @autoUpdateManager.getState()
|
||||
|
||||
ipcMain.on 'execute-javascript-in-dev-tools', (event, code) ->
|
||||
event.sender.devToolsWebContents?.executeJavaScript(code)
|
||||
|
||||
setupDockMenu: ->
|
||||
if process.platform is 'darwin'
|
||||
dockMenu = Menu.buildFromTemplate [
|
||||
|
||||
@@ -39,16 +39,24 @@ class AutoUpdateManager
|
||||
|
||||
autoUpdater.on 'checking-for-update', =>
|
||||
@setState(CheckingState)
|
||||
@emitWindowEvent('checking-for-update')
|
||||
|
||||
autoUpdater.on 'update-not-available', =>
|
||||
@setState(NoUpdateAvailableState)
|
||||
@emitWindowEvent('update-not-available')
|
||||
|
||||
autoUpdater.on 'update-available', =>
|
||||
@setState(DownladingState)
|
||||
# We use sendMessage to send an event called 'update-available' in 'update-downloaded'
|
||||
# once the update download is complete. This mismatch between the electron
|
||||
# autoUpdater events is unfortunate but in the interest of not changing the
|
||||
# one existing event handled by applicationDelegate
|
||||
@emitWindowEvent('did-begin-downloading-update')
|
||||
@emit('did-begin-download')
|
||||
|
||||
autoUpdater.on 'update-downloaded', (event, releaseNotes, @releaseVersion) =>
|
||||
@setState(UpdateAvailableState)
|
||||
@emitUpdateAvailableEvent(@getWindows()...)
|
||||
@emitUpdateAvailableEvent()
|
||||
|
||||
@config.onDidChange 'core.automaticallyUpdate', ({newValue}) =>
|
||||
if newValue
|
||||
@@ -64,10 +72,14 @@ class AutoUpdateManager
|
||||
when 'linux'
|
||||
@setState(UnsupportedState)
|
||||
|
||||
emitUpdateAvailableEvent: (windows...) ->
|
||||
emitUpdateAvailableEvent: ->
|
||||
return unless @releaseVersion?
|
||||
for atomWindow in windows
|
||||
atomWindow.sendMessage('update-available', {@releaseVersion})
|
||||
@emitWindowEvent('update-available', {@releaseVersion})
|
||||
return
|
||||
|
||||
emitWindowEvent: (eventName, payload) ->
|
||||
for atomWindow in @getWindows()
|
||||
atomWindow.sendMessage(eventName, payload)
|
||||
return
|
||||
|
||||
setState: (state) ->
|
||||
|
||||
Reference in New Issue
Block a user