diff --git a/build/Gruntfile.coffee b/build/Gruntfile.coffee index 63afc2fd7..60d5916b8 100644 --- a/build/Gruntfile.coffee +++ b/build/Gruntfile.coffee @@ -38,7 +38,6 @@ module.exports = (grunt) -> buildDir = grunt.option('build-dir') buildDir ?= path.join(os.tmpdir(), 'atom-build') buildDir = path.resolve(buildDir) - disableAutoUpdate = grunt.option('no-auto-update') ? false channel = grunt.option('channel') releasableBranches = ['stable', 'beta'] @@ -181,7 +180,7 @@ module.exports = (grunt) -> pkg: grunt.file.readJSON('package.json') atom: { - appName, channel, metadata, disableAutoUpdate, + appName, channel, metadata, appFileName, apmFileName, appDir, buildDir, contentsDir, installDir, shellAppDir, symbolsDir, } @@ -257,7 +256,7 @@ module.exports = (grunt) -> outputDir: 'electron' downloadDir: electronDownloadDir rebuild: true # rebuild native modules after electron is updated - token: process.env.ATOM_ACCESS_TOKEN + token: process.env.ATOM_ACCESS_TOKEN ? 'da809a6077bb1b0aa7c5623f7b2d5f1fec2faae4' 'create-windows-installer': installer: diff --git a/build/tasks/build-task.coffee b/build/tasks/build-task.coffee index 213aa0da4..a86c7c1f4 100644 --- a/build/tasks/build-task.coffee +++ b/build/tasks/build-task.coffee @@ -186,5 +186,4 @@ module.exports = (grunt) -> dependencies = ['compile', 'generate-license:save', 'generate-module-cache', 'compile-packages-slug'] dependencies.push('copy-info-plist') if process.platform is 'darwin' dependencies.push('set-exe-icon') if process.platform is 'win32' - dependencies.push('disable-autoupdate') if grunt.config.get('atom.disableAutoUpdate') grunt.task.run(dependencies...) diff --git a/build/tasks/disable-autoupdate-task.coffee b/build/tasks/disable-autoupdate-task.coffee deleted file mode 100644 index 7517543da..000000000 --- a/build/tasks/disable-autoupdate-task.coffee +++ /dev/null @@ -1,12 +0,0 @@ -fs = require 'fs' -path = require 'path' - -module.exports = (grunt) -> - - grunt.registerTask 'disable-autoupdate', 'Set up disableAutoUpdate field in package.json file', -> - appDir = fs.realpathSync(grunt.config.get('atom.appDir')) - - metadata = grunt.file.readJSON(path.join(appDir, 'package.json')) - metadata._disableAutoUpdate = grunt.config.get('atom.disableAutoUpdate') - - grunt.file.write(path.join(appDir, 'package.json'), JSON.stringify(metadata)) diff --git a/dot-atom/.gitignore b/dot-atom/.gitignore index 81af3f689..e5c80ce23 100644 --- a/dot-atom/.gitignore +++ b/dot-atom/.gitignore @@ -1,5 +1,6 @@ -storage +blob-store compile-cache dev -.npm +storage .node-gyp +.npm diff --git a/menus/darwin.cson b/menus/darwin.cson index 52b7a5bc8..0195bde71 100644 --- a/menus/darwin.cson +++ b/menus/darwin.cson @@ -139,7 +139,6 @@ { label: 'View' submenu: [ - { label: 'Reload', command: 'window:reload' } { label: 'Toggle Full Screen', command: 'window:toggle-full-screen' } { label: 'Panes' @@ -164,6 +163,7 @@ label: 'Developer' submenu: [ { label: 'Open In Dev Mode…', command: 'application:open-dev' } + { label: 'Reload Window', command: 'window:reload' } { label: 'Run Package Specs', command: 'window:run-package-specs' } { label: 'Toggle Developer Tools', command: 'window:toggle-dev-tools' } ] diff --git a/menus/linux.cson b/menus/linux.cson index fa831b4a4..9f251d67f 100644 --- a/menus/linux.cson +++ b/menus/linux.cson @@ -95,7 +95,6 @@ { label: '&View' submenu: [ - { label: '&Reload', command: 'window:reload' } { label: 'Toggle &Full Screen', command: 'window:toggle-full-screen' } { label: 'Toggle Menu Bar', command: 'window:toggle-menu-bar' } { @@ -121,6 +120,7 @@ label: 'Developer' submenu: [ { label: 'Open In &Dev Mode…', command: 'application:open-dev' } + { label: '&Reload Window', command: 'window:reload' } { label: 'Run Package &Specs', command: 'window:run-package-specs' } { label: 'Toggle Developer &Tools', command: 'window:toggle-dev-tools' } ] diff --git a/menus/win32.cson b/menus/win32.cson index 04da3d388..ad0461898 100644 --- a/menus/win32.cson +++ b/menus/win32.cson @@ -94,7 +94,6 @@ { label: '&View' submenu: [ - { label: '&Reload', command: 'window:reload' } { label: 'Toggle &Full Screen', command: 'window:toggle-full-screen' } { label: 'Toggle Menu Bar', command: 'window:toggle-menu-bar' } { @@ -120,6 +119,7 @@ label: 'Developer' submenu: [ { label: 'Open In &Dev Mode…', command: 'application:open-dev' } + { label: '&Reload Window', command: 'window:reload' } { label: 'Run Package &Specs', command: 'window:run-package-specs' } { label: 'Toggle Developer &Tools', command: 'window:toggle-dev-tools' } ] diff --git a/package.json b/package.json index aef164c39..3b67d6a5d 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "service-hub": "^0.7.0", "source-map-support": "^0.3.2", "temp": "0.8.1", - "text-buffer": "8.1.3", + "text-buffer": "8.1.4", "typescript-simple": "1.0.0", "underscore-plus": "^1.6.6", "yargs": "^3.23.0" @@ -65,9 +65,9 @@ "base16-tomorrow-dark-theme": "1.1.0", "base16-tomorrow-light-theme": "1.1.1", "one-dark-ui": "1.1.9", - "one-dark-syntax": "1.1.1", - "one-light-syntax": "1.1.1", "one-light-ui": "1.1.9", + "one-dark-syntax": "1.1.2", + "one-light-syntax": "1.1.2", "solarized-dark-syntax": "0.39.0", "solarized-light-syntax": "0.23.0", "about": "1.1.0", @@ -128,7 +128,7 @@ "language-hyperlink": "0.16.0", "language-java": "0.17.0", "language-javascript": "0.105.0", - "language-json": "0.17.2", + "language-json": "0.17.3", "language-less": "0.29.0", "language-make": "0.21.0", "language-mustache": "0.13.0", @@ -136,7 +136,7 @@ "language-perl": "0.32.0", "language-php": "0.36.0", "language-property-list": "0.8.0", - "language-python": "0.42.1", + "language-python": "0.43.0", "language-ruby": "0.67.0", "language-ruby-on-rails": "0.25.0", "language-sass": "0.45.0", diff --git a/spec/config-spec.coffee b/spec/config-spec.coffee index eab2f6f04..c431fea5f 100644 --- a/spec/config-spec.coffee +++ b/spec/config-spec.coffee @@ -872,6 +872,26 @@ describe "Config", -> atom.config.loadUserConfig() expect(atom.config.get("foo.bar")).toBe "baz" + describe "when the config file fails to load", -> + addErrorHandler = null + + beforeEach -> + atom.notifications.onDidAddNotification addErrorHandler = jasmine.createSpy() + spyOn(fs, "existsSync").andCallFake -> + error = new Error() + error.code = 'EPERM' + throw error + + it "creates a notification and does not try to save later changes to disk", -> + load = -> atom.config.loadUserConfig() + expect(load).not.toThrow() + expect(addErrorHandler.callCount).toBe 1 + + atom.config.set("foo.bar", "baz") + advanceClock(100) + expect(atom.config.save).not.toHaveBeenCalled() + expect(atom.config.get("foo.bar")).toBe "baz" + describe ".observeUserConfig()", -> updatedHandler = null diff --git a/src/browser/application-menu.coffee b/src/browser/application-menu.coffee index fef9f90fa..b0a6e3267 100644 --- a/src/browser/application-menu.coffee +++ b/src/browser/application-menu.coffee @@ -101,8 +101,6 @@ class ApplicationMenu downloadingUpdateItem.visible = false installUpdateItem.visible = false - return if @autoUpdateManager.isDisabled() - switch state when 'idle', 'error', 'no-update-available' checkForUpdateItem.visible = true @@ -117,9 +115,10 @@ class ApplicationMenu # # Returns an Array of menu item Objects. getDefaultTemplate: -> - template = [ + [ 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()} @@ -127,10 +126,6 @@ class ApplicationMenu ] ] - # Add `Check for Update` button if autoUpdateManager is enabled. - template[0].submenu.unshift({label: "Check for Update", metadata: {autoUpdate: true}}) unless @autoUpdateManager.isDisabled() - template - focusedWindow: -> _.find global.atomApplication.windows, (atomWindow) -> atomWindow.isFocused() diff --git a/src/browser/atom-application.coffee b/src/browser/atom-application.coffee index 954eb868d..d68141f5c 100644 --- a/src/browser/atom-application.coffee +++ b/src/browser/atom-application.coffee @@ -69,8 +69,7 @@ class AtomApplication @pidsToOpenWindows = {} @windows = [] - disableAutoUpdate = require(path.join(@resourcePath, 'package.json'))._disableAutoUpdate ? false - @autoUpdateManager = new AutoUpdateManager(@version, options.test, disableAutoUpdate) + @autoUpdateManager = new AutoUpdateManager(@version, options.test, @resourcePath) @applicationMenu = new ApplicationMenu(@version, @autoUpdateManager) @atomProtocolHandler = new AtomProtocolHandler(@resourcePath, @safeMode) diff --git a/src/browser/auto-update-manager.coffee b/src/browser/auto-update-manager.coffee index 63a79b60f..2df338761 100644 --- a/src/browser/auto-update-manager.coffee +++ b/src/browser/auto-update-manager.coffee @@ -1,5 +1,6 @@ autoUpdater = null _ = require 'underscore-plus' +Config = require '../config' {EventEmitter} = require 'events' path = require 'path' @@ -15,10 +16,13 @@ module.exports = class AutoUpdateManager _.extend @prototype, EventEmitter.prototype - constructor: (@version, @testMode, @disabled) -> + constructor: (@version, @testMode, resourcePath) -> @state = IdleState @iconPath = path.resolve(__dirname, '..', '..', 'resources', 'atom.png') @feedUrl = "https://atom.io/api/updates?version=#{@version}" + @config = new Config({configDirPath: process.env.ATOM_HOME, resourcePath, enablePersistence: true}) + @config.setSchema null, {type: 'object', properties: _.clone(require('../config-schema'))} + @config.load() process.nextTick => @setupAutoUpdater() setupAutoUpdater: -> @@ -46,9 +50,13 @@ class AutoUpdateManager @setState(UpdateAvailableState) @emitUpdateAvailableEvent(@getWindows()...) - # Only check for updates periodically if enabled and running in release - # version. - @scheduleUpdateCheck() unless /\w{7}/.test(@version) or @disabled + @config.onDidChange 'core.automaticallyUpdate', ({newValue}) => + if newValue + @scheduleUpdateCheck() + else + @cancelScheduledUpdateCheck() + + @scheduleUpdateCheck() if @config.get 'core.automaticallyUpdate' switch process.platform when 'win32' @@ -56,9 +64,6 @@ class AutoUpdateManager when 'linux' @setState(UnsupportedState) - isDisabled: -> - @disabled - emitUpdateAvailableEvent: (windows...) -> return unless @releaseVersion? for atomWindow in windows @@ -74,10 +79,18 @@ class AutoUpdateManager @state scheduleUpdateCheck: -> - checkForUpdates = => @check(hidePopups: true) - fourHours = 1000 * 60 * 60 * 4 - setInterval(checkForUpdates, fourHours) - checkForUpdates() + # Only schedule update check periodically if running in release version and + # and there is no existing scheduled update check. + unless /\w{7}/.test(@version) or @checkForUpdatesIntervalID + checkForUpdates = => @check(hidePopups: true) + fourHours = 1000 * 60 * 60 * 4 + @checkForUpdatesIntervalID = setInterval(checkForUpdates, fourHours) + checkForUpdates() + + cancelScheduledUpdateCheck: -> + if @checkForUpdatesIntervalID + clearInterval(@checkForUpdatesIntervalID) + @checkForUpdatesIntervalID = null check: ({hidePopups}={}) -> unless hidePopups diff --git a/src/config-schema.coffee b/src/config-schema.coffee index d9c0c1d21..88e00c71d 100644 --- a/src/config-schema.coffee +++ b/src/config-schema.coffee @@ -104,6 +104,10 @@ module.exports = description: 'Automatically open an empty editor on startup.' type: 'boolean' default: true + automaticallyUpdate: + description: 'Automatically update Atom when a new release is available.' + type: 'boolean' + default: true editor: type: 'object' diff --git a/src/config.coffee b/src/config.coffee index 2e4387732..2a883a57d 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -779,9 +779,14 @@ class Config loadUserConfig: -> return if @shouldNotAccessFileSystem() - unless fs.existsSync(@configFilePath) - fs.makeTreeSync(path.dirname(@configFilePath)) - CSON.writeFileSync(@configFilePath, {}) + try + unless fs.existsSync(@configFilePath) + fs.makeTreeSync(path.dirname(@configFilePath)) + CSON.writeFileSync(@configFilePath, {}) + catch error + @configFileHasErrors = true + @notifyFailure("Failed to initialize `#{path.basename(@configFilePath)}`", error.stack) + return try unless @savePending @@ -820,7 +825,7 @@ class Config @watchSubscription = null notifyFailure: (errorMessage, detail) -> - @notificationManager.addError(errorMessage, {detail, dismissable: true}) + @notificationManager?.addError(errorMessage, {detail, dismissable: true}) save: -> return if @shouldNotAccessFileSystem() diff --git a/src/pane.coffee b/src/pane.coffee index 412fc5251..c006e29fe 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -721,30 +721,28 @@ class Pane extends Model message = "#{message} '#{itemPath}'" if itemPath @notificationManager.addWarning(message, options) - if error.code is 'EISDIR' or error.message?.endsWith?('is a directory') + customMessage = @getMessageForErrorCode(error.code) + if customMessage? + addWarningWithPath("Unable to save file: #{customMessage}") + else if error.code is 'EISDIR' or error.message?.endsWith?('is a directory') @notificationManager.addWarning("Unable to save file: #{error.message}") - else if error.code is 'EACCES' - addWarningWithPath('Unable to save file: Permission denied') else if error.code in ['EPERM', 'EBUSY', 'UNKNOWN', 'EEXIST', 'ELOOP', 'EAGAIN'] addWarningWithPath('Unable to save file', detail: error.message) - else if error.code is 'EROFS' - addWarningWithPath('Unable to save file: Read-only file system') - else if error.code is 'ENOSPC' - addWarningWithPath('Unable to save file: No space left on device') - else if error.code is 'ENXIO' - addWarningWithPath('Unable to save file: No such device or address') - else if error.code is 'ENOTSUP' - addWarningWithPath('Unable to save file: Operation not supported on socket') - else if error.code is 'EIO' - addWarningWithPath('Unable to save file: I/O error writing file') - else if error.code is 'EINTR' - addWarningWithPath('Unable to save file: Interrupted system call') - else if error.code is 'ECONNRESET' - addWarningWithPath('Unable to save file: Connection reset') - else if error.code is 'ESPIPE' - addWarningWithPath('Unable to save file: Invalid seek') else if errorMatch = /ENOTDIR, not a directory '([^']+)'/.exec(error.message) fileName = errorMatch[1] @notificationManager.addWarning("Unable to save file: A directory in the path '#{fileName}' could not be written to") else throw error + + getMessageForErrorCode: (errorCode) -> + switch errorCode + when 'EACCES' then 'Permission denied' + when 'ECONNRESET' then 'Connection reset' + when 'EINTR' then 'Interrupted system call' + when 'EIO' then 'I/O error writing file' + when 'ENOSPC' then 'No space left on device' + when 'ENOTSUP' then 'Operation not supported on socket' + when 'ENXIO' then 'No such device or address' + when 'EROFS' then 'Read-only file system' + when 'ESPIPE' then 'Invalid seek' + when 'ETIMEDOUT' then 'Connection timed out' diff --git a/src/project.coffee b/src/project.coffee index d59c041cb..714511f9a 100644 --- a/src/project.coffee +++ b/src/project.coffee @@ -288,7 +288,7 @@ class Project extends Model 'atom.repository-provider', '^0.1.0', (provider) => - @repositoryProviders.push(provider) + @repositoryProviders.unshift(provider) @setPaths(@getPaths()) if null in @repositories new Disposable => @repositoryProviders.splice(@repositoryProviders.indexOf(provider), 1) diff --git a/src/workspace.coffee b/src/workspace.coffee index f64f58ee0..07e69cdcf 100644 --- a/src/workspace.coffee +++ b/src/workspace.coffee @@ -475,7 +475,7 @@ class Workspace extends Model when 'EACCES' @notificationManager.addWarning("Permission denied '#{error.path}'") return Promise.resolve() - when 'EPERM', 'EBUSY', 'ENXIO', 'EIO', 'ENOTCONN', 'UNKNOWN', 'ECONNRESET', 'EINVAL', 'EMFILE', 'ENOTDIR' + when 'EPERM', 'EBUSY', 'ENXIO', 'EIO', 'ENOTCONN', 'UNKNOWN', 'ECONNRESET', 'EINVAL', 'EMFILE', 'ENOTDIR', 'EAGAIN' @notificationManager.addWarning("Unable to open '#{error.path ? uri}'", detail: error.message) return Promise.resolve() else