diff --git a/menus/darwin.cson b/menus/darwin.cson index 01c4283d2..517a33b06 100644 --- a/menus/darwin.cson +++ b/menus/darwin.cson @@ -4,10 +4,10 @@ submenu: [ { label: 'About Atom', command: 'application:about' } { label: 'View License', command: 'application:open-license' } - { label: "VERSION", enabled: false } - { label: "Restart and Install Update", command: 'application:install-update', visible: false} - { label: "Check for Update", command: 'application:check-for-update', visible: false} - { label: "Downloading Update", command: 'application:check-for-update', enabled: false, visible: false} + { label: 'VERSION', enabled: false } + { label: 'Restart and Install Update', command: 'application:install-update', visible: false} + { label: 'Check for Update', command: 'application:check-for-update', visible: false} + { label: 'Downloading Update', enabled: false, visible: false} { type: 'separator' } { label: 'Preferences...', command: 'application:show-settings' } { label: 'Open Your Config', command: 'application:open-your-config' } diff --git a/src/browser/application-menu.coffee b/src/browser/application-menu.coffee index 5aa611666..730c0e17d 100644 --- a/src/browser/application-menu.coffee +++ b/src/browser/application-menu.coffee @@ -12,6 +12,8 @@ class ApplicationMenu constructor: (@version) -> @menu = Menu.buildFromTemplate @getDefaultTemplate() Menu.setApplicationMenu @menu + global.atomApplication.autoUpdateManager.on 'state-changed', (state) => + @showUpdateMenuItem(state) # Public: Updates the entire menu with the given keybindings. # @@ -26,6 +28,8 @@ class ApplicationMenu @menu = Menu.buildFromTemplate(template) Menu.setApplicationMenu(@menu) + @showUpdateMenuItem(global.atomApplication.autoUpdateManager.getState()) + # Flattens the given menu and submenu items into an single Array. # # * menu: @@ -63,35 +67,28 @@ class ApplicationMenu # Replaces VERSION with the current version. substituteVersion: (template) -> - if (item = _.find(@flattenMenuTemplate(template), (i) -> i.label == 'VERSION')) + if (item = _.find(@flattenMenuTemplate(template), ({label}) -> label == 'VERSION')) item.label = "Version #{@version}" - # Toggles Install Update Item - showInstallUpdateItem: (visible=true) -> - if visible - @showDownloadingUpdateItem(false) - @showCheckForUpdateItem(false) + # Sets the proper visible state the update menu items + showUpdateMenuItem: (state) -> + checkForUpdateItem = _.find(@flattenMenuItems(@menu), ({label}) -> label == 'Check for Update') + downloadingUpdateItem = _.find(@flattenMenuItems(@menu), ({label}) -> label == 'Downloading Update') + installUpdateItem = _.find(@flattenMenuItems(@menu), ({label}) -> label == 'Restart and Install Update') - if (item = _.find(@flattenMenuItems(@menu), (i) -> i.label == 'Restart and Install Update')) - item.visible = visible + return unless checkForUpdateItem? and downloadingUpdateItem? and installUpdateItem? - # Toggles Downloading Update Item - showDownloadingUpdateItem: (visible=true) -> - if visible - @showInstallUpdateItem(false) - @showCheckForUpdateItem(false) + checkForUpdateItem.visible = false + downloadingUpdateItem.visible = false + installUpdateItem.visible = false - if (item = _.find(@flattenMenuItems(@menu), (i) -> i.label == 'Downloading Update')) - item.visible = visible - - # Toggles Check For Update Item - showCheckForUpdateItem: (visible=true) -> - if visible - @showDownloadingUpdateItem(false) - @showInstallUpdateItem(false) - - if (item = _.find(@flattenMenuItems(@menu), (i) -> i.label == 'Check for Update')) - item.visible = visible + switch state + when 'idle', 'error', 'no-update-available' + checkForUpdateItem.visible = true + when 'checking', 'downloading' + downloadingUpdateItem.visible = true + when 'update-available' + installUpdateItem.visible = true # Default list of menu items. # @@ -100,6 +97,7 @@ class ApplicationMenu [ label: "Atom" submenu: [ + { label: "Check for Update", metadata: {autoUpdate: true}} { label: 'Reload', accelerator: 'Command+R', click: => @focusedWindow()?.reload() } { label: 'Close Window', accelerator: 'Command+Shift+W', click: => @focusedWindow()?.close() } { label: 'Toggle Dev Tools', accelerator: 'Command+Alt+I', click: => @focusedWindow()?.toggleDevTools() } @@ -122,7 +120,7 @@ class ApplicationMenu # Returns a complete menu configuration object for atom-shell's menu API. translateTemplate: (template, keystrokesByCommand) -> template.forEach (item) => - item.metadata = {} + item.metadata ?= {} if item.command item.accelerator = @acceleratorForCommand(item.command, keystrokesByCommand) item.click = => global.atomApplication.sendCommand(item.command) diff --git a/src/browser/atom-application.coffee b/src/browser/atom-application.coffee index c0f5636e8..1f789f10e 100644 --- a/src/browser/atom-application.coffee +++ b/src/browser/atom-application.coffee @@ -1,11 +1,10 @@ AtomWindow = require './atom-window' ApplicationMenu = require './application-menu' AtomProtocolHandler = require './atom-protocol-handler' +AutoUpdateManager = require './auto-update-manager' BrowserWindow = require 'browser-window' Menu = require 'menu' -autoUpdater = require 'auto-updater' app = require 'app' -dialog = require 'dialog' fs = require 'fs' ipc = require 'ipc' path = require 'path' @@ -66,13 +65,13 @@ class AtomApplication @pathsToOpen ?= [] @windows = [] + @autoUpdateManager = new AutoUpdateManager(@version) @applicationMenu = new ApplicationMenu(@version) @atomProtocolHandler = new AtomProtocolHandler(@resourcePath) @listenForArgumentsFromNewProcess() @setupJavaScriptArguments() @handleEvents() - @setupAutoUpdater() @openWithOptions(options) @@ -96,6 +95,8 @@ class AtomApplication addWindow: (window) -> @windows.push window @applicationMenu?.enableWindowSpecificItems(true) + window.once 'window:loaded', => + @autoUpdateManager.emitUpdateAvailableEvent(window) # Creates server to listen for additional atom application launches. # @@ -127,46 +128,6 @@ class AtomApplication setupJavaScriptArguments: -> app.commandLine.appendSwitch 'js-flags', '--harmony' - # Enable updates unless running from a local build of Atom. - setupAutoUpdater: -> - return if /\w{7}/.test(@version) # Only released versions should check for updates. - - autoUpdater.setFeedUrl "https://atom.io/api/updates?version=#{@version}" - - autoUpdater.on 'checking-for-update', => - @applicationMenu.showDownloadingUpdateItem(false) - @applicationMenu.showInstallUpdateItem(false) - @applicationMenu.showCheckForUpdateItem(false) - - autoUpdater.on 'update-not-available', => - @applicationMenu.showCheckForUpdateItem(true) - - autoUpdater.on 'update-available', => - @applicationMenu.showDownloadingUpdateItem(true) - - autoUpdater.on 'update-downloaded', (event, releaseNotes, releaseVersion, releaseDate, releaseURL) => - atomWindow.sendCommand('window:update-available', [releaseVersion, releaseNotes]) for atomWindow in @windows - @applicationMenu.showInstallUpdateItem(true) - - autoUpdater.on 'error', (event, message) => - @applicationMenu.showCheckForUpdateItem(true) - - # Check for update after Atom has fully started and the menus are created - setTimeout((-> autoUpdater.checkForUpdates()), 5000) - - checkForUpdate: -> - @onUpdateNotAvailable ?= => - autoUpdater.removeListener 'error', @onUpdateError - dialog.showMessageBox type: 'info', buttons: ['OK'], message: 'No update available.', detail: "Version #{@version} is the latest version." - - @onUpdateError ?= (event, message) => - autoUpdater.removeListener 'update-not-available', @onUpdateNotAvailable - dialog.showMessageBox type: 'warning', buttons: ['OK'], message: 'There was an error checking for updates.', detail: message - - autoUpdater.once 'update-not-available', @onUpdateNotAvailable - autoUpdater.once 'error', @onUpdateError - autoUpdater.checkForUpdates() - # Registers basic application commands, non-idempotent. handleEvents: -> @on 'application:run-all-specs', -> @runSpecs(exitWhenDone: false, resourcePath: global.devResourcePath) @@ -178,8 +139,8 @@ class AtomApplication @on 'application:open-dev', -> @promptForPath(devMode: true) @on 'application:inspect', ({x,y}) -> @focusedWindow().browserWindow.inspectElement(x, y) @on 'application:open-documentation', -> shell.openExternal('https://atom.io/docs/latest/?app') - @on 'application:install-update', -> autoUpdater.quitAndInstall() - @on 'application:check-for-update', => @checkForUpdate() + @on 'application:install-update', -> @autoUpdateManager.install() + @on 'application:check-for-update', => @autoUpdateManager.check() if process.platform is 'darwin' @on 'application:about', -> Menu.sendActionToFirstResponder('orderFrontStandardAboutPanel:') diff --git a/src/browser/atom-window.coffee b/src/browser/atom-window.coffee index fed11e4d6..f7d89535d 100644 --- a/src/browser/atom-window.coffee +++ b/src/browser/atom-window.coffee @@ -7,9 +7,12 @@ path = require 'path' fs = require 'fs' url = require 'url' _ = require 'underscore-plus' +{EventEmitter} = require 'events' module.exports = class AtomWindow + _.extend @prototype, EventEmitter.prototype + @iconPath: path.resolve(__dirname, '..', '..', 'resources', 'atom.png') @includeShellLoadTime: true @@ -38,7 +41,10 @@ class AtomWindow loadSettings.initialPath = path.dirname(pathToOpen) @browserWindow.loadSettings = loadSettings - @browserWindow.once 'window:loaded', => @loaded = true + @browserWindow.once 'window:loaded', => + @emit 'window:loaded' + @loaded = true + @browserWindow.loadUrl @getUrl(loadSettings) @browserWindow.focusOnWebView() if @isSpec diff --git a/src/browser/auto-update-manager.coffee b/src/browser/auto-update-manager.coffee new file mode 100644 index 000000000..cb76c2481 --- /dev/null +++ b/src/browser/auto-update-manager.coffee @@ -0,0 +1,76 @@ +autoUpdater = require 'auto-updater' +dialog = require 'dialog' +_ = require 'underscore-plus' +{EventEmitter} = require 'events' + +IDLE_STATE='idle' +CHECKING_STATE='checking' +DOWNLOADING_STATE='downloading' +UPDATE_AVAILABLE_STATE='update-available' +NO_UPDATE_AVAILABLE_STATE='no-update-available' +ERROR_STATE='error' + +module.exports = +class AutoUpdateManager + _.extend @prototype, EventEmitter.prototype + + constructor: (@version) -> + @state = IDLE_STATE + + # Only released versions should check for updates. + return if /\w{7}/.test(@version) + + autoUpdater.setFeedUrl "https://atom.io/api/updates?version=#{@version}" + + autoUpdater.on 'checking-for-update', => + @setState(CHECKING_STATE) + + autoUpdater.on 'update-not-available', => + @setState(NO_UPDATE_AVAILABLE_STATE) + + autoUpdater.on 'update-available', => + @setState(DOWNLOADING_STATE) + + autoUpdater.on 'error', (event, message) => + @setState(ERROR_STATE) + console.error "Error Downloading Update: #{message}" + + autoUpdater.on 'update-downloaded', (event, @releaseNotes, @releaseVersion) => + @setState(UPDATE_AVAILABLE_STATE) + @emitUpdateAvailableEvent(@getWindows()...) + + @check(hidePopups: true) + + emitUpdateAvailableEvent: (windows...) -> + return unless @releaseVersion? and @releaseNotes + for atomWindow in windows + atomWindow.sendCommand('window:update-available', [@releaseVersion, @releaseNotes]) + + setState: (state) -> + return unless @state != state + @state = state + @emit 'state-changed', @state + + getState: -> + @state + + check: ({hidePopups}={})-> + unless hidePopups + autoUpdater.once 'update-not-available', @onUpdateNotAvailable + autoUpdater.once 'error', @onUpdateError + + autoUpdater.checkForUpdates() + + install: -> + autoUpdater.quitAndInstall() + + onUpdateNotAvailable: => + autoUpdater.removeListener 'error', @onUpdateError + dialog.showMessageBox type: 'info', buttons: ['OK'], message: 'No update available.', detail: "Version #{@version} is the latest version." + + onUpdateError: (event, message) => + autoUpdater.removeListener 'update-not-available', @onUpdateNotAvailable + dialog.showMessageBox type: 'warning', buttons: ['OK'], message: 'There was an error checking for updates.', detail: message + + getWindows: -> + global.atomApplication.windows