mirror of
https://github.com/atom/atom.git
synced 2026-04-06 03:02:13 -04:00
Convert remaining coffee-script code in main process code to javascript
This commit is contained in:
@@ -1,143 +0,0 @@
|
||||
autoUpdater = null
|
||||
{EventEmitter} = require 'events'
|
||||
path = require 'path'
|
||||
|
||||
IdleState = 'idle'
|
||||
CheckingState = 'checking'
|
||||
DownloadingState = 'downloading'
|
||||
UpdateAvailableState = 'update-available'
|
||||
NoUpdateAvailableState = 'no-update-available'
|
||||
UnsupportedState = 'unsupported'
|
||||
ErrorState = 'error'
|
||||
|
||||
module.exports =
|
||||
class AutoUpdateManager
|
||||
Object.assign @prototype, EventEmitter.prototype
|
||||
|
||||
constructor: (@version, @testMode, @config) ->
|
||||
@state = IdleState
|
||||
@iconPath = path.resolve(__dirname, '..', '..', 'resources', 'atom.png')
|
||||
|
||||
initialize: ->
|
||||
if process.platform is 'win32'
|
||||
archSuffix = if process.arch is 'ia32' then '' else '-' + process.arch
|
||||
@feedUrl = "https://atom.io/api/updates#{archSuffix}?version=#{@version}"
|
||||
autoUpdater = require './auto-updater-win32'
|
||||
else
|
||||
@feedUrl = "https://atom.io/api/updates?version=#{@version}"
|
||||
{autoUpdater} = require 'electron'
|
||||
|
||||
autoUpdater.on 'error', (event, message) =>
|
||||
@setState(ErrorState, message)
|
||||
@emitWindowEvent('update-error')
|
||||
console.error "Error Downloading Update: #{message}"
|
||||
|
||||
autoUpdater.setFeedURL @feedUrl
|
||||
|
||||
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(DownloadingState)
|
||||
# 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()
|
||||
|
||||
@config.onDidChange 'core.automaticallyUpdate', ({newValue}) =>
|
||||
if newValue
|
||||
@scheduleUpdateCheck()
|
||||
else
|
||||
@cancelScheduledUpdateCheck()
|
||||
|
||||
@scheduleUpdateCheck() if @config.get 'core.automaticallyUpdate'
|
||||
|
||||
switch process.platform
|
||||
when 'win32'
|
||||
@setState(UnsupportedState) unless autoUpdater.supportsUpdates()
|
||||
when 'linux'
|
||||
@setState(UnsupportedState)
|
||||
|
||||
emitUpdateAvailableEvent: ->
|
||||
return unless @releaseVersion?
|
||||
@emitWindowEvent('update-available', {@releaseVersion})
|
||||
return
|
||||
|
||||
emitWindowEvent: (eventName, payload) ->
|
||||
for atomWindow in @getWindows()
|
||||
atomWindow.sendMessage(eventName, payload)
|
||||
return
|
||||
|
||||
setState: (state, errorMessage) ->
|
||||
return if @state is state
|
||||
@state = state
|
||||
@errorMessage = errorMessage
|
||||
@emit 'state-changed', @state
|
||||
|
||||
getState: ->
|
||||
@state
|
||||
|
||||
getErrorMessage: ->
|
||||
@errorMessage
|
||||
|
||||
scheduleUpdateCheck: ->
|
||||
# Only schedule update check periodically if running in release version and
|
||||
# and there is no existing scheduled update check.
|
||||
unless /-dev/.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
|
||||
autoUpdater.once 'update-not-available', @onUpdateNotAvailable
|
||||
autoUpdater.once 'error', @onUpdateError
|
||||
|
||||
autoUpdater.checkForUpdates() unless @testMode
|
||||
|
||||
install: ->
|
||||
autoUpdater.quitAndInstall() unless @testMode
|
||||
|
||||
onUpdateNotAvailable: =>
|
||||
autoUpdater.removeListener 'error', @onUpdateError
|
||||
{dialog} = require 'electron'
|
||||
dialog.showMessageBox {
|
||||
type: 'info'
|
||||
buttons: ['OK']
|
||||
icon: @iconPath
|
||||
message: 'No update available.'
|
||||
title: 'No Update Available'
|
||||
detail: "Version #{@version} is the latest version."
|
||||
}, -> # noop callback to get async behavior
|
||||
|
||||
onUpdateError: (event, message) =>
|
||||
autoUpdater.removeListener 'update-not-available', @onUpdateNotAvailable
|
||||
{dialog} = require 'electron'
|
||||
dialog.showMessageBox {
|
||||
type: 'warning'
|
||||
buttons: ['OK']
|
||||
icon: @iconPath
|
||||
message: 'There was an error checking for updates.'
|
||||
title: 'Update Error'
|
||||
detail: message
|
||||
}, -> # noop callback to get async behavior
|
||||
|
||||
getWindows: ->
|
||||
global.atomApplication.getAllWindows()
|
||||
178
src/main-process/auto-update-manager.js
Normal file
178
src/main-process/auto-update-manager.js
Normal file
@@ -0,0 +1,178 @@
|
||||
const {EventEmitter} = require('events')
|
||||
const path = require('path')
|
||||
|
||||
const IdleState = 'idle'
|
||||
const CheckingState = 'checking'
|
||||
const DownloadingState = 'downloading'
|
||||
const UpdateAvailableState = 'update-available'
|
||||
const NoUpdateAvailableState = 'no-update-available'
|
||||
const UnsupportedState = 'unsupported'
|
||||
const ErrorState = 'error'
|
||||
|
||||
let autoUpdater = null
|
||||
|
||||
module.exports =
|
||||
class AutoUpdateManager extends EventEmitter {
|
||||
constructor (version, testMode, config) {
|
||||
super()
|
||||
this.onUpdateNotAvailable = this.onUpdateNotAvailable.bind(this)
|
||||
this.onUpdateError = this.onUpdateError.bind(this)
|
||||
this.version = version
|
||||
this.testMode = testMode
|
||||
this.config = config
|
||||
this.state = IdleState
|
||||
this.iconPath = path.resolve(__dirname, '..', '..', 'resources', 'atom.png')
|
||||
}
|
||||
|
||||
initialize () {
|
||||
if (process.platform === 'win32') {
|
||||
const archSuffix = process.arch === 'ia32' ? '' : `-${process.arch}`
|
||||
this.feedUrl = `https://atom.io/api/updates${archSuffix}?version=${this.version}`
|
||||
autoUpdater = require('./auto-updater-win32')
|
||||
} else {
|
||||
this.feedUrl = `https://atom.io/api/updates?version=${this.version}`;
|
||||
({autoUpdater} = require('electron'))
|
||||
}
|
||||
|
||||
autoUpdater.on('error', (event, message) => {
|
||||
this.setState(ErrorState, message)
|
||||
this.emitWindowEvent('update-error')
|
||||
console.error(`Error Downloading Update: ${message}`)
|
||||
})
|
||||
|
||||
autoUpdater.setFeedURL(this.feedUrl)
|
||||
|
||||
autoUpdater.on('checking-for-update', () => {
|
||||
this.setState(CheckingState)
|
||||
this.emitWindowEvent('checking-for-update')
|
||||
})
|
||||
|
||||
autoUpdater.on('update-not-available', () => {
|
||||
this.setState(NoUpdateAvailableState)
|
||||
this.emitWindowEvent('update-not-available')
|
||||
})
|
||||
|
||||
autoUpdater.on('update-available', () => {
|
||||
this.setState(DownloadingState)
|
||||
// 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
|
||||
this.emitWindowEvent('did-begin-downloading-update')
|
||||
this.emit('did-begin-download')
|
||||
})
|
||||
|
||||
autoUpdater.on('update-downloaded', (event, releaseNotes, releaseVersion) => {
|
||||
this.releaseVersion = releaseVersion
|
||||
this.setState(UpdateAvailableState)
|
||||
this.emitUpdateAvailableEvent()
|
||||
})
|
||||
|
||||
this.config.onDidChange('core.automaticallyUpdate', ({newValue}) => {
|
||||
if (newValue) {
|
||||
this.scheduleUpdateCheck()
|
||||
} else {
|
||||
this.cancelScheduledUpdateCheck()
|
||||
}
|
||||
})
|
||||
|
||||
if (this.config.get('core.automaticallyUpdate')) this.scheduleUpdateCheck()
|
||||
|
||||
switch (process.platform) {
|
||||
case 'win32':
|
||||
if (!autoUpdater.supportsUpdates()) {
|
||||
this.setState(UnsupportedState)
|
||||
}
|
||||
break
|
||||
case 'linux':
|
||||
this.setState(UnsupportedState)
|
||||
}
|
||||
}
|
||||
|
||||
emitUpdateAvailableEvent () {
|
||||
if (this.releaseVersion == null) return
|
||||
this.emitWindowEvent('update-available', {releaseVersion: this.releaseVersion})
|
||||
}
|
||||
|
||||
emitWindowEvent (eventName, payload) {
|
||||
for (let atomWindow of this.getWindows()) {
|
||||
atomWindow.sendMessage(eventName, payload)
|
||||
}
|
||||
}
|
||||
|
||||
setState (state, errorMessage) {
|
||||
if (this.state === state) return
|
||||
this.state = state
|
||||
this.errorMessage = errorMessage
|
||||
this.emit('state-changed', this.state)
|
||||
}
|
||||
|
||||
getState () {
|
||||
return this.state
|
||||
}
|
||||
|
||||
getErrorMessage () {
|
||||
return this.errorMessage
|
||||
}
|
||||
|
||||
scheduleUpdateCheck () {
|
||||
// Only schedule update check periodically if running in release version and
|
||||
// and there is no existing scheduled update check.
|
||||
if (!/-dev/.test(this.version) && !this.checkForUpdatesIntervalID) {
|
||||
const checkForUpdates = () => this.check({hidePopups: true})
|
||||
const fourHours = 1000 * 60 * 60 * 4
|
||||
this.checkForUpdatesIntervalID = setInterval(checkForUpdates, fourHours)
|
||||
checkForUpdates()
|
||||
}
|
||||
}
|
||||
|
||||
cancelScheduledUpdateCheck () {
|
||||
if (this.checkForUpdatesIntervalID) {
|
||||
clearInterval(this.checkForUpdatesIntervalID)
|
||||
this.checkForUpdatesIntervalID = null
|
||||
}
|
||||
}
|
||||
|
||||
check ({hidePopups} = {}) {
|
||||
if (!hidePopups) {
|
||||
autoUpdater.once('update-not-available', this.onUpdateNotAvailable)
|
||||
autoUpdater.once('error', this.onUpdateError)
|
||||
}
|
||||
|
||||
if (!this.testMode) autoUpdater.checkForUpdates()
|
||||
}
|
||||
|
||||
install () {
|
||||
if (!this.testMode) autoUpdater.quitAndInstall()
|
||||
}
|
||||
|
||||
onUpdateNotAvailable () {
|
||||
autoUpdater.removeListener('error', this.onUpdateError)
|
||||
const {dialog} = require('electron')
|
||||
dialog.showMessageBox({
|
||||
type: 'info',
|
||||
buttons: ['OK'],
|
||||
icon: this.iconPath,
|
||||
message: 'No update available.',
|
||||
title: 'No Update Available',
|
||||
detail: `Version ${this.version} is the latest version.`
|
||||
}, () => {}) // noop callback to get async behavior
|
||||
}
|
||||
|
||||
onUpdateError (event, message) {
|
||||
autoUpdater.removeListener('update-not-available', this.onUpdateNotAvailable)
|
||||
const {dialog} = require('electron')
|
||||
dialog.showMessageBox({
|
||||
type: 'warning',
|
||||
buttons: ['OK'],
|
||||
icon: this.iconPath,
|
||||
message: 'There was an error checking for updates.',
|
||||
title: 'Update Error',
|
||||
detail: message
|
||||
}, () => {}) // noop callback to get async behavior
|
||||
}
|
||||
|
||||
getWindows () {
|
||||
return global.atomApplication.getAllWindows()
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
{EventEmitter} = require 'events'
|
||||
SquirrelUpdate = require './squirrel-update'
|
||||
|
||||
class AutoUpdater
|
||||
Object.assign @prototype, EventEmitter.prototype
|
||||
|
||||
setFeedURL: (@updateUrl) ->
|
||||
|
||||
quitAndInstall: ->
|
||||
if SquirrelUpdate.existsSync()
|
||||
SquirrelUpdate.restartAtom(require('electron').app)
|
||||
else
|
||||
require('electron').autoUpdater.quitAndInstall()
|
||||
|
||||
downloadUpdate: (callback) ->
|
||||
SquirrelUpdate.spawn ['--download', @updateUrl], (error, stdout) ->
|
||||
return callback(error) if error?
|
||||
|
||||
try
|
||||
# Last line of output is the JSON details about the releases
|
||||
json = stdout.trim().split('\n').pop()
|
||||
update = JSON.parse(json)?.releasesToApply?.pop?()
|
||||
catch error
|
||||
error.stdout = stdout
|
||||
return callback(error)
|
||||
|
||||
callback(null, update)
|
||||
|
||||
installUpdate: (callback) ->
|
||||
SquirrelUpdate.spawn(['--update', @updateUrl], callback)
|
||||
|
||||
supportsUpdates: ->
|
||||
SquirrelUpdate.existsSync()
|
||||
|
||||
checkForUpdates: ->
|
||||
throw new Error('Update URL is not set') unless @updateUrl
|
||||
|
||||
@emit 'checking-for-update'
|
||||
|
||||
unless SquirrelUpdate.existsSync()
|
||||
@emit 'update-not-available'
|
||||
return
|
||||
|
||||
@downloadUpdate (error, update) =>
|
||||
if error?
|
||||
@emit 'update-not-available'
|
||||
return
|
||||
|
||||
unless update?
|
||||
@emit 'update-not-available'
|
||||
return
|
||||
|
||||
@emit 'update-available'
|
||||
|
||||
@installUpdate (error) =>
|
||||
if error?
|
||||
@emit 'update-not-available'
|
||||
return
|
||||
|
||||
@emit 'update-downloaded', {}, update.releaseNotes, update.version, new Date(), 'https://atom.io', => @quitAndInstall()
|
||||
|
||||
module.exports = new AutoUpdater()
|
||||
88
src/main-process/auto-updater-win32.js
Normal file
88
src/main-process/auto-updater-win32.js
Normal file
@@ -0,0 +1,88 @@
|
||||
const {EventEmitter} = require('events')
|
||||
const SquirrelUpdate = require('./squirrel-update')
|
||||
|
||||
class AutoUpdater extends EventEmitter {
|
||||
setFeedURL (updateUrl) {
|
||||
this.updateUrl = updateUrl
|
||||
}
|
||||
|
||||
quitAndInstall () {
|
||||
if (SquirrelUpdate.existsSync()) {
|
||||
SquirrelUpdate.restartAtom(require('electron').app)
|
||||
} else {
|
||||
require('electron').autoUpdater.quitAndInstall()
|
||||
}
|
||||
}
|
||||
|
||||
downloadUpdate (callback) {
|
||||
SquirrelUpdate.spawn(['--download', this.updateUrl], function (error, stdout) {
|
||||
let update
|
||||
if (error != null) return callback(error)
|
||||
|
||||
try {
|
||||
// Last line of output is the JSON details about the releases
|
||||
const json = stdout.trim().split('\n').pop()
|
||||
const data = JSON.parse(json)
|
||||
const releasesToApply = data && data.releasesToApply
|
||||
if (releasesToApply.pop) update = releasesToApply.pop()
|
||||
} catch (error) {
|
||||
error.stdout = stdout
|
||||
return callback(error)
|
||||
}
|
||||
|
||||
callback(null, update)
|
||||
})
|
||||
}
|
||||
|
||||
installUpdate (callback) {
|
||||
SquirrelUpdate.spawn(['--update', this.updateUrl], callback)
|
||||
}
|
||||
|
||||
supportsUpdates () {
|
||||
SquirrelUpdate.existsSync()
|
||||
}
|
||||
|
||||
checkForUpdates () {
|
||||
if (!this.updateUrl) throw new Error('Update URL is not set')
|
||||
|
||||
this.emit('checking-for-update')
|
||||
|
||||
if (!SquirrelUpdate.existsSync()) {
|
||||
this.emit('update-not-available')
|
||||
return
|
||||
}
|
||||
|
||||
this.downloadUpdate((error, update) => {
|
||||
if (error != null) {
|
||||
this.emit('update-not-available')
|
||||
return
|
||||
}
|
||||
|
||||
if (update == null) {
|
||||
this.emit('update-not-available')
|
||||
return
|
||||
}
|
||||
|
||||
this.emit('update-available')
|
||||
|
||||
this.installUpdate(error => {
|
||||
if (error != null) {
|
||||
this.emit('update-not-available')
|
||||
return
|
||||
}
|
||||
|
||||
this.emit(
|
||||
'update-downloaded',
|
||||
{},
|
||||
update.releaseNotes,
|
||||
update.version,
|
||||
new Date(),
|
||||
'https://atom.io',
|
||||
() => this.quitAndInstall()
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new AutoUpdater()
|
||||
@@ -1,24 +0,0 @@
|
||||
{Menu} = require 'electron'
|
||||
|
||||
module.exports =
|
||||
class ContextMenu
|
||||
constructor: (template, @atomWindow) ->
|
||||
template = @createClickHandlers(template)
|
||||
menu = Menu.buildFromTemplate(template)
|
||||
menu.popup(@atomWindow.browserWindow, {async: true})
|
||||
|
||||
# It's necessary to build the event handlers in this process, otherwise
|
||||
# closures are dragged across processes and failed to be garbage collected
|
||||
# appropriately.
|
||||
createClickHandlers: (template) ->
|
||||
for item in template
|
||||
if item.command
|
||||
item.commandDetail ?= {}
|
||||
item.commandDetail.contextCommand = true
|
||||
item.commandDetail.atomWindow = @atomWindow
|
||||
do (item) =>
|
||||
item.click = =>
|
||||
global.atomApplication.sendCommandToWindow(item.command, @atomWindow, item.commandDetail)
|
||||
else if item.submenu
|
||||
@createClickHandlers(item.submenu)
|
||||
item
|
||||
33
src/main-process/context-menu.js
Normal file
33
src/main-process/context-menu.js
Normal file
@@ -0,0 +1,33 @@
|
||||
const {Menu} = require('electron')
|
||||
|
||||
module.exports =
|
||||
class ContextMenu {
|
||||
constructor (template, atomWindow) {
|
||||
this.atomWindow = atomWindow
|
||||
this.createClickHandlers(template)
|
||||
const menu = Menu.buildFromTemplate(template)
|
||||
menu.popup(this.atomWindow.browserWindow, {async: true})
|
||||
}
|
||||
|
||||
// It's necessary to build the event handlers in this process, otherwise
|
||||
// closures are dragged across processes and failed to be garbage collected
|
||||
// appropriately.
|
||||
createClickHandlers (template) {
|
||||
template.forEach(item => {
|
||||
if (item.command) {
|
||||
if (!item.commandDetail) item.commandDetail = {}
|
||||
item.commandDetail.contextCommand = true
|
||||
item.commandDetail.atomWindow = this.atomWindow
|
||||
item.click = () => {
|
||||
global.atomApplication.sendCommandToWindow(
|
||||
item.command,
|
||||
this.atomWindow,
|
||||
item.commandDetail
|
||||
)
|
||||
}
|
||||
} else if (item.submenu) {
|
||||
this.createClickHandlers(item.submenu)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
ChildProcess = require 'child_process'
|
||||
|
||||
# Spawn a command and invoke the callback when it completes with an error
|
||||
# and the output from standard out.
|
||||
#
|
||||
# * `command` The underlying OS command {String} to execute.
|
||||
# * `args` (optional) The {Array} with arguments to be passed to command.
|
||||
# * `callback` (optional) The {Function} to call after the command has run. It will be invoked with arguments:
|
||||
# * `error` (optional) An {Error} object returned by the command, `null` if no error was thrown.
|
||||
# * `code` Error code returned by the command.
|
||||
# * `stdout` The {String} output text generated by the command.
|
||||
# * `stdout` The {String} output text generated by the command.
|
||||
#
|
||||
# Returns `undefined`.
|
||||
exports.spawn = (command, args, callback) ->
|
||||
stdout = ''
|
||||
|
||||
try
|
||||
spawnedProcess = ChildProcess.spawn(command, args)
|
||||
catch error
|
||||
# Spawn can throw an error
|
||||
process.nextTick -> callback?(error, stdout)
|
||||
return
|
||||
|
||||
spawnedProcess.stdout.on 'data', (data) -> stdout += data
|
||||
|
||||
error = null
|
||||
spawnedProcess.on 'error', (processError) -> error ?= processError
|
||||
spawnedProcess.on 'close', (code, signal) ->
|
||||
error ?= new Error("Command failed: #{signal ? code}") if code isnt 0
|
||||
error?.code ?= code
|
||||
error?.stdout ?= stdout
|
||||
callback?(error, stdout)
|
||||
# This is necessary if using Powershell 2 on Windows 7 to get the events to raise
|
||||
# http://stackoverflow.com/questions/9155289/calling-powershell-from-nodejs
|
||||
spawnedProcess.stdin.end()
|
||||
43
src/main-process/spawner.js
Normal file
43
src/main-process/spawner.js
Normal file
@@ -0,0 +1,43 @@
|
||||
const ChildProcess = require('child_process')
|
||||
|
||||
// Spawn a command and invoke the callback when it completes with an error
|
||||
// and the output from standard out.
|
||||
//
|
||||
// * `command` The underlying OS command {String} to execute.
|
||||
// * `args` (optional) The {Array} with arguments to be passed to command.
|
||||
// * `callback` (optional) The {Function} to call after the command has run. It will be invoked with arguments:
|
||||
// * `error` (optional) An {Error} object returned by the command, `null` if no error was thrown.
|
||||
// * `code` Error code returned by the command.
|
||||
// * `stdout` The {String} output text generated by the command.
|
||||
// * `stdout` The {String} output text generated by the command.
|
||||
exports.spawn = function (command, args, callback) {
|
||||
let error
|
||||
let spawnedProcess
|
||||
let stdout = ''
|
||||
|
||||
try {
|
||||
spawnedProcess = ChildProcess.spawn(command, args)
|
||||
} catch (error) {
|
||||
process.nextTick(() => callback && callback(error, stdout))
|
||||
return
|
||||
}
|
||||
|
||||
spawnedProcess.stdout.on('data', data => { stdout += data })
|
||||
spawnedProcess.on('error', processError => { error = processError })
|
||||
spawnedProcess.on('close', (code, signal) => {
|
||||
if (!error && code !== 0) {
|
||||
error = new Error(`Command failed: ${signal != null ? signal : code}`)
|
||||
}
|
||||
|
||||
if (error) {
|
||||
if (error.code == null) error.code = code
|
||||
if (error.stdout == null) error.stdout = stdout
|
||||
}
|
||||
|
||||
callback && callback(error, stdout)
|
||||
})
|
||||
|
||||
// This is necessary if using Powershell 2 on Windows 7 to get the events to raise
|
||||
// http://stackoverflow.com/questions/9155289/calling-powershell-from-nodejs
|
||||
return spawnedProcess.stdin.end()
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
fs = require 'fs-plus'
|
||||
path = require 'path'
|
||||
Spawner = require './spawner'
|
||||
WinShell = require './win-shell'
|
||||
WinPowerShell = require './win-powershell'
|
||||
|
||||
appFolder = path.resolve(process.execPath, '..')
|
||||
rootAtomFolder = path.resolve(appFolder, '..')
|
||||
binFolder = path.join(rootAtomFolder, 'bin')
|
||||
updateDotExe = path.join(rootAtomFolder, 'Update.exe')
|
||||
exeName = path.basename(process.execPath)
|
||||
|
||||
if process.env.SystemRoot
|
||||
system32Path = path.join(process.env.SystemRoot, 'System32')
|
||||
setxPath = path.join(system32Path, 'setx.exe')
|
||||
else
|
||||
setxPath = 'setx.exe'
|
||||
|
||||
# Spawn setx.exe and callback when it completes
|
||||
spawnSetx = (args, callback) ->
|
||||
Spawner.spawn(setxPath, args, callback)
|
||||
|
||||
# Spawn the Update.exe with the given arguments and invoke the callback when
|
||||
# the command completes.
|
||||
spawnUpdate = (args, callback) ->
|
||||
Spawner.spawn(updateDotExe, args, callback)
|
||||
|
||||
# Add atom and apm to the PATH
|
||||
#
|
||||
# This is done by adding .cmd shims to the root bin folder in the Atom
|
||||
# install directory that point to the newly installed versions inside
|
||||
# the versioned app directories.
|
||||
addCommandsToPath = (callback) ->
|
||||
installCommands = (callback) ->
|
||||
atomCommandPath = path.join(binFolder, 'atom.cmd')
|
||||
relativeAtomPath = path.relative(binFolder, path.join(appFolder, 'resources', 'cli', 'atom.cmd'))
|
||||
atomCommand = "@echo off\r\n\"%~dp0\\#{relativeAtomPath}\" %*"
|
||||
|
||||
atomShCommandPath = path.join(binFolder, 'atom')
|
||||
relativeAtomShPath = path.relative(binFolder, path.join(appFolder, 'resources', 'cli', 'atom.sh'))
|
||||
atomShCommand = "#!/bin/sh\r\n\"$(dirname \"$0\")/#{relativeAtomShPath.replace(/\\/g, '/')}\" \"$@\"\r\necho"
|
||||
|
||||
apmCommandPath = path.join(binFolder, 'apm.cmd')
|
||||
relativeApmPath = path.relative(binFolder, path.join(process.resourcesPath, 'app', 'apm', 'bin', 'apm.cmd'))
|
||||
apmCommand = "@echo off\r\n\"%~dp0\\#{relativeApmPath}\" %*"
|
||||
|
||||
apmShCommandPath = path.join(binFolder, 'apm')
|
||||
relativeApmShPath = path.relative(binFolder, path.join(appFolder, 'resources', 'cli', 'apm.sh'))
|
||||
apmShCommand = "#!/bin/sh\r\n\"$(dirname \"$0\")/#{relativeApmShPath.replace(/\\/g, '/')}\" \"$@\""
|
||||
|
||||
fs.writeFile atomCommandPath, atomCommand, ->
|
||||
fs.writeFile atomShCommandPath, atomShCommand, ->
|
||||
fs.writeFile apmCommandPath, apmCommand, ->
|
||||
fs.writeFile apmShCommandPath, apmShCommand, ->
|
||||
callback()
|
||||
|
||||
addBinToPath = (pathSegments, callback) ->
|
||||
pathSegments.push(binFolder)
|
||||
newPathEnv = pathSegments.join(';')
|
||||
spawnSetx(['Path', newPathEnv], callback)
|
||||
|
||||
installCommands (error) ->
|
||||
return callback(error) if error?
|
||||
|
||||
WinPowerShell.getPath (error, pathEnv) ->
|
||||
return callback(error) if error?
|
||||
|
||||
pathSegments = pathEnv.split(/;+/).filter (pathSegment) -> pathSegment
|
||||
if pathSegments.indexOf(binFolder) is -1
|
||||
addBinToPath(pathSegments, callback)
|
||||
else
|
||||
callback()
|
||||
|
||||
# Remove atom and apm from the PATH
|
||||
removeCommandsFromPath = (callback) ->
|
||||
WinPowerShell.getPath (error, pathEnv) ->
|
||||
return callback(error) if error?
|
||||
|
||||
pathSegments = pathEnv.split(/;+/).filter (pathSegment) ->
|
||||
pathSegment and pathSegment isnt binFolder
|
||||
newPathEnv = pathSegments.join(';')
|
||||
|
||||
if pathEnv isnt newPathEnv
|
||||
spawnSetx(['Path', newPathEnv], callback)
|
||||
else
|
||||
callback()
|
||||
|
||||
# Create a desktop and start menu shortcut by using the command line API
|
||||
# provided by Squirrel's Update.exe
|
||||
createShortcuts = (locations, callback) ->
|
||||
spawnUpdate(['--createShortcut', exeName, '-l', locations.join(',')], callback)
|
||||
|
||||
# Update the desktop and start menu shortcuts by using the command line API
|
||||
# provided by Squirrel's Update.exe
|
||||
updateShortcuts = (callback) ->
|
||||
if homeDirectory = fs.getHomeDirectory()
|
||||
desktopShortcutPath = path.join(homeDirectory, 'Desktop', 'Atom.lnk')
|
||||
# Check if the desktop shortcut has been previously deleted and
|
||||
# and keep it deleted if it was
|
||||
fs.exists desktopShortcutPath, (desktopShortcutExists) ->
|
||||
locations = ['StartMenu']
|
||||
locations.push 'Desktop' if desktopShortcutExists
|
||||
|
||||
createShortcuts locations, callback
|
||||
else
|
||||
createShortcuts ['Desktop', 'StartMenu'], callback
|
||||
|
||||
# Remove the desktop and start menu shortcuts by using the command line API
|
||||
# provided by Squirrel's Update.exe
|
||||
removeShortcuts = (callback) ->
|
||||
spawnUpdate(['--removeShortcut', exeName], callback)
|
||||
|
||||
exports.spawn = spawnUpdate
|
||||
|
||||
# Is the Update.exe installed with Atom?
|
||||
exports.existsSync = ->
|
||||
fs.existsSync(updateDotExe)
|
||||
|
||||
# Restart Atom using the version pointed to by the atom.cmd shim
|
||||
exports.restartAtom = (app) ->
|
||||
if projectPath = global.atomApplication?.lastFocusedWindow?.projectPath
|
||||
args = [projectPath]
|
||||
app.once 'will-quit', -> Spawner.spawn(path.join(binFolder, 'atom.cmd'), args)
|
||||
app.quit()
|
||||
|
||||
updateContextMenus = (callback) ->
|
||||
WinShell.fileContextMenu.update ->
|
||||
WinShell.folderContextMenu.update ->
|
||||
WinShell.folderBackgroundContextMenu.update ->
|
||||
callback()
|
||||
|
||||
# Handle squirrel events denoted by --squirrel-* command line arguments.
|
||||
exports.handleStartupEvent = (app, squirrelCommand) ->
|
||||
switch squirrelCommand
|
||||
when '--squirrel-install'
|
||||
createShortcuts ['Desktop', 'StartMenu'], ->
|
||||
addCommandsToPath ->
|
||||
WinShell.fileHandler.register ->
|
||||
updateContextMenus ->
|
||||
app.quit()
|
||||
true
|
||||
when '--squirrel-updated'
|
||||
updateShortcuts ->
|
||||
addCommandsToPath ->
|
||||
WinShell.fileHandler.update ->
|
||||
updateContextMenus ->
|
||||
app.quit()
|
||||
true
|
||||
when '--squirrel-uninstall'
|
||||
removeShortcuts ->
|
||||
removeCommandsFromPath ->
|
||||
WinShell.fileHandler.deregister ->
|
||||
WinShell.fileContextMenu.deregister ->
|
||||
WinShell.folderContextMenu.deregister ->
|
||||
WinShell.folderBackgroundContextMenu.deregister ->
|
||||
app.quit()
|
||||
true
|
||||
when '--squirrel-obsolete'
|
||||
app.quit()
|
||||
true
|
||||
else
|
||||
false
|
||||
187
src/main-process/squirrel-update.js
Normal file
187
src/main-process/squirrel-update.js
Normal file
@@ -0,0 +1,187 @@
|
||||
let setxPath
|
||||
const fs = require('fs-plus')
|
||||
const path = require('path')
|
||||
const Spawner = require('./spawner')
|
||||
const WinShell = require('./win-shell')
|
||||
const WinPowerShell = require('./win-powershell')
|
||||
|
||||
const appFolder = path.resolve(process.execPath, '..')
|
||||
const rootAtomFolder = path.resolve(appFolder, '..')
|
||||
const binFolder = path.join(rootAtomFolder, 'bin')
|
||||
const updateDotExe = path.join(rootAtomFolder, 'Update.exe')
|
||||
const exeName = path.basename(process.execPath)
|
||||
|
||||
if (process.env.SystemRoot) {
|
||||
const system32Path = path.join(process.env.SystemRoot, 'System32')
|
||||
setxPath = path.join(system32Path, 'setx.exe')
|
||||
} else {
|
||||
setxPath = 'setx.exe'
|
||||
}
|
||||
|
||||
// Spawn setx.exe and callback when it completes
|
||||
const spawnSetx = (args, callback) => Spawner.spawn(setxPath, args, callback)
|
||||
|
||||
// Spawn the Update.exe with the given arguments and invoke the callback when
|
||||
// the command completes.
|
||||
const spawnUpdate = (args, callback) => Spawner.spawn(updateDotExe, args, callback)
|
||||
|
||||
// Add atom and apm to the PATH
|
||||
//
|
||||
// This is done by adding .cmd shims to the root bin folder in the Atom
|
||||
// install directory that point to the newly installed versions inside
|
||||
// the versioned app directories.
|
||||
const addCommandsToPath = callback => {
|
||||
const installCommands = callback => {
|
||||
const atomCommandPath = path.join(binFolder, 'atom.cmd')
|
||||
const relativeAtomPath = path.relative(binFolder, path.join(appFolder, 'resources', 'cli', 'atom.cmd'))
|
||||
const atomCommand = `@echo off\r\n\"%~dp0\\${relativeAtomPath}\" %*`
|
||||
|
||||
const atomShCommandPath = path.join(binFolder, 'atom')
|
||||
const relativeAtomShPath = path.relative(binFolder, path.join(appFolder, 'resources', 'cli', 'atom.sh'))
|
||||
const atomShCommand = `#!/bin/sh\r\n\"$(dirname \"$0\")/${relativeAtomShPath.replace(/\\/g, '/')}\" \"$@\"\r\necho`
|
||||
|
||||
const apmCommandPath = path.join(binFolder, 'apm.cmd')
|
||||
const relativeApmPath = path.relative(binFolder, path.join(process.resourcesPath, 'app', 'apm', 'bin', 'apm.cmd'))
|
||||
const apmCommand = `@echo off\r\n\"%~dp0\\${relativeApmPath}\" %*`
|
||||
|
||||
const apmShCommandPath = path.join(binFolder, 'apm')
|
||||
const relativeApmShPath = path.relative(binFolder, path.join(appFolder, 'resources', 'cli', 'apm.sh'))
|
||||
const apmShCommand = `#!/bin/sh\r\n\"$(dirname \"$0\")/${relativeApmShPath.replace(/\\/g, '/')}\" \"$@\"`
|
||||
|
||||
fs.writeFile(atomCommandPath, atomCommand, () =>
|
||||
fs.writeFile(atomShCommandPath, atomShCommand, () =>
|
||||
fs.writeFile(apmCommandPath, apmCommand, () =>
|
||||
fs.writeFile(apmShCommandPath, apmShCommand, () => callback())
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
const addBinToPath = (pathSegments, callback) => {
|
||||
pathSegments.push(binFolder)
|
||||
const newPathEnv = pathSegments.join(';')
|
||||
spawnSetx(['Path', newPathEnv], callback)
|
||||
}
|
||||
|
||||
installCommands(error => {
|
||||
if (error) return callback(error)
|
||||
|
||||
WinPowerShell.getPath((error, pathEnv) => {
|
||||
if (error) return callback(error)
|
||||
|
||||
const pathSegments = pathEnv.split(/;+/).filter(pathSegment => pathSegment)
|
||||
if (pathSegments.indexOf(binFolder) === -1) {
|
||||
addBinToPath(pathSegments, callback)
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Remove atom and apm from the PATH
|
||||
const removeCommandsFromPath = callback =>
|
||||
WinPowerShell.getPath((error, pathEnv) => {
|
||||
if (error != null) { return callback(error) }
|
||||
|
||||
const pathSegments = pathEnv.split(/;+/).filter(pathSegment => pathSegment && (pathSegment !== binFolder))
|
||||
const newPathEnv = pathSegments.join(';')
|
||||
|
||||
if (pathEnv !== newPathEnv) {
|
||||
return spawnSetx(['Path', newPathEnv], callback)
|
||||
} else {
|
||||
return callback()
|
||||
}
|
||||
})
|
||||
|
||||
// Create a desktop and start menu shortcut by using the command line API
|
||||
// provided by Squirrel's Update.exe
|
||||
const createShortcuts = (locations, callback) => spawnUpdate(['--createShortcut', exeName, '-l', locations.join(',')], callback)
|
||||
|
||||
// Update the desktop and start menu shortcuts by using the command line API
|
||||
// provided by Squirrel's Update.exe
|
||||
const updateShortcuts = (callback) => {
|
||||
const homeDirectory = fs.getHomeDirectory()
|
||||
if (homeDirectory) {
|
||||
const desktopShortcutPath = path.join(homeDirectory, 'Desktop', 'Atom.lnk')
|
||||
// Check if the desktop shortcut has been previously deleted and
|
||||
// and keep it deleted if it was
|
||||
fs.exists(desktopShortcutPath, (desktopShortcutExists) => {
|
||||
const locations = ['StartMenu']
|
||||
if (desktopShortcutExists) { locations.push('Desktop') }
|
||||
|
||||
createShortcuts(locations, callback)
|
||||
})
|
||||
} else {
|
||||
createShortcuts(['Desktop', 'StartMenu'], callback)
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the desktop and start menu shortcuts by using the command line API
|
||||
// provided by Squirrel's Update.exe
|
||||
const removeShortcuts = callback => spawnUpdate(['--removeShortcut', exeName], callback)
|
||||
|
||||
exports.spawn = spawnUpdate
|
||||
|
||||
// Is the Update.exe installed with Atom?
|
||||
exports.existsSync = () => fs.existsSync(updateDotExe)
|
||||
|
||||
// Restart Atom using the version pointed to by the atom.cmd shim
|
||||
exports.restartAtom = (app) => {
|
||||
let args
|
||||
if (global.atomApplication && global.atomApplication.lastFocusedWindow) {
|
||||
const {projectPath} = global.atomApplication.lastFocusedWindow
|
||||
if (projectPath) args = [projectPath]
|
||||
}
|
||||
app.once('will-quit', () => Spawner.spawn(path.join(binFolder, 'atom.cmd'), args))
|
||||
app.quit()
|
||||
}
|
||||
|
||||
const updateContextMenus = callback =>
|
||||
WinShell.fileContextMenu.update(() =>
|
||||
WinShell.folderContextMenu.update(() =>
|
||||
WinShell.folderBackgroundContextMenu.update(() => callback())
|
||||
)
|
||||
)
|
||||
|
||||
// Handle squirrel events denoted by --squirrel-* command line arguments.
|
||||
exports.handleStartupEvent = (app, squirrelCommand) => {
|
||||
switch (squirrelCommand) {
|
||||
case '--squirrel-install':
|
||||
createShortcuts(['Desktop', 'StartMenu'], () =>
|
||||
addCommandsToPath(() =>
|
||||
WinShell.fileHandler.register(() =>
|
||||
updateContextMenus(() => app.quit())
|
||||
)
|
||||
)
|
||||
)
|
||||
return true
|
||||
case '--squirrel-updated':
|
||||
updateShortcuts(() =>
|
||||
addCommandsToPath(() =>
|
||||
WinShell.fileHandler.update(() =>
|
||||
updateContextMenus(() => app.quit())
|
||||
)
|
||||
)
|
||||
)
|
||||
return true
|
||||
case '--squirrel-uninstall':
|
||||
removeShortcuts(() =>
|
||||
removeCommandsFromPath(() =>
|
||||
WinShell.fileHandler.deregister(() =>
|
||||
WinShell.fileContextMenu.deregister(() =>
|
||||
WinShell.folderContextMenu.deregister(() =>
|
||||
WinShell.folderBackgroundContextMenu.deregister(() => app.quit())
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
return true
|
||||
case '--squirrel-obsolete':
|
||||
app.quit()
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
44
src/main-process/win-powershell.js
Normal file
44
src/main-process/win-powershell.js
Normal file
@@ -0,0 +1,44 @@
|
||||
let powershellPath
|
||||
const path = require('path')
|
||||
const Spawner = require('./spawner')
|
||||
|
||||
if (process.env.SystemRoot) {
|
||||
const system32Path = path.join(process.env.SystemRoot, 'System32')
|
||||
powershellPath = path.join(system32Path, 'WindowsPowerShell', 'v1.0', 'powershell.exe')
|
||||
} else {
|
||||
powershellPath = 'powershell.exe'
|
||||
}
|
||||
|
||||
// Spawn powershell.exe and callback when it completes
|
||||
const spawnPowershell = function (args, callback) {
|
||||
// Set encoding and execute the command, capture the output, and return it
|
||||
// via .NET's console in order to have consistent UTF-8 encoding.
|
||||
// See http://stackoverflow.com/questions/22349139/utf-8-output-from-powershell
|
||||
// to address https://github.com/atom/atom/issues/5063
|
||||
args[0] = `\
|
||||
[Console]::OutputEncoding=[System.Text.Encoding]::UTF8
|
||||
$output=${args[0]}
|
||||
[Console]::WriteLine($output)\
|
||||
`
|
||||
args.unshift('-command')
|
||||
args.unshift('RemoteSigned')
|
||||
args.unshift('-ExecutionPolicy')
|
||||
args.unshift('-noprofile')
|
||||
Spawner.spawn(powershellPath, args, callback)
|
||||
}
|
||||
|
||||
// Get the user's PATH environment variable registry value.
|
||||
//
|
||||
// * `callback` The {Function} to call after registry operation is done.
|
||||
// It will be invoked with the same arguments provided by {Spawner.spawn}.
|
||||
//
|
||||
// Returns the user's path {String}.
|
||||
exports.getPath = callback =>
|
||||
spawnPowershell(['[environment]::GetEnvironmentVariable(\'Path\',\'User\')'], function (error, stdout) {
|
||||
if (error != null) {
|
||||
return callback(error)
|
||||
}
|
||||
|
||||
const pathOutput = stdout.replace(/^\s+|\s+$/g, '')
|
||||
return callback(null, pathOutput)
|
||||
})
|
||||
Reference in New Issue
Block a user