mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-02-25 00:25:03 +08:00
Remove ancient Atom Task, BufferedProcess, Color support in config
This commit is contained in:
parent
d2238d2a4a
commit
5809d9bf47
10 changed files with 2 additions and 686 deletions
|
@ -1,75 +0,0 @@
|
|||
ChildProcess = require 'child_process'
|
||||
path = require 'path'
|
||||
BufferedProcess = require '../src/buffered-process'
|
||||
|
||||
describe "BufferedProcess", ->
|
||||
describe "when a bad command is specified", ->
|
||||
[oldOnError] = []
|
||||
beforeEach ->
|
||||
oldOnError = window.onerror
|
||||
window.onerror = jasmine.createSpy()
|
||||
|
||||
afterEach ->
|
||||
window.onerror = oldOnError
|
||||
|
||||
describe "when there is an error handler specified", ->
|
||||
it "calls the error handler and does not throw an exception", ->
|
||||
p = new BufferedProcess
|
||||
command: 'bad-command-nope'
|
||||
args: ['nothing']
|
||||
options: {}
|
||||
|
||||
errorSpy = jasmine.createSpy().andCallFake (error) -> error.handle()
|
||||
p.onWillThrowError(errorSpy)
|
||||
|
||||
waitsFor -> errorSpy.callCount > 0
|
||||
|
||||
runs ->
|
||||
expect(window.onerror).not.toHaveBeenCalled()
|
||||
expect(errorSpy).toHaveBeenCalled()
|
||||
expect(errorSpy.mostRecentCall.args[0].error.message).toContain 'spawn bad-command-nope ENOENT'
|
||||
|
||||
# describe "when there is not an error handler specified", ->
|
||||
# it "calls the error handler and does not throw an exception", ->
|
||||
# spyOn(process, "nextTick").andCallFake (fn) -> fn()
|
||||
#
|
||||
# try
|
||||
# p = new BufferedProcess
|
||||
# command: 'bad-command-nope'
|
||||
# args: ['nothing']
|
||||
# options: {stdout: 'ignore'}
|
||||
#
|
||||
# catch error
|
||||
# expect(error.message).toContain 'Failed to spawn command `bad-command-nope`'
|
||||
# expect(error.name).toBe 'BufferedProcessError'
|
||||
|
||||
describe "on Windows", ->
|
||||
originalPlatform = null
|
||||
|
||||
beforeEach ->
|
||||
# Prevent any commands from actually running and affecting the host
|
||||
originalSpawn = ChildProcess.spawn
|
||||
spyOn(ChildProcess, 'spawn').andCallFake ->
|
||||
# Just spawn something that won't actually modify the host
|
||||
if originalPlatform is 'win32'
|
||||
originalSpawn('dir')
|
||||
else
|
||||
originalSpawn('ls')
|
||||
|
||||
originalPlatform = process.platform
|
||||
Object.defineProperty process, 'platform', value: 'win32'
|
||||
|
||||
afterEach ->
|
||||
Object.defineProperty process, 'platform', value: originalPlatform
|
||||
|
||||
describe "when the explorer command is spawned on Windows", ->
|
||||
it "doesn't quote arguments of the form /root,C...", ->
|
||||
new BufferedProcess({command: 'explorer.exe', args: ['/root,C:\\foo']})
|
||||
expect(ChildProcess.spawn.argsForCall[0][1][2]).toBe '"explorer.exe /root,C:\\foo"'
|
||||
|
||||
it "spawns the command using a cmd.exe wrapper", ->
|
||||
new BufferedProcess({command: 'dir'})
|
||||
expect(path.basename(ChildProcess.spawn.argsForCall[0][0])).toBe 'cmd.exe'
|
||||
expect(ChildProcess.spawn.argsForCall[0][1][0]).toBe '/s'
|
||||
expect(ChildProcess.spawn.argsForCall[0][1][1]).toBe '/c'
|
||||
expect(ChildProcess.spawn.argsForCall[0][1][2]).toBe '"dir"'
|
|
@ -120,9 +120,6 @@ class N1SpecRunner {
|
|||
NylasEnv.storeWindowDimensions();
|
||||
return NylasEnv.saveSync();
|
||||
});
|
||||
|
||||
// On load this will extend the window object
|
||||
require('../../src/window');
|
||||
}
|
||||
|
||||
_addReporters() {
|
||||
|
|
|
@ -1,244 +0,0 @@
|
|||
_ = require 'underscore'
|
||||
ChildProcess = require 'child_process'
|
||||
{Emitter} = require 'event-kit'
|
||||
path = require 'path'
|
||||
|
||||
# Extended: A wrapper which provides standard error/output line buffering for
|
||||
# Node's ChildProcess.
|
||||
#
|
||||
# ## Examples
|
||||
#
|
||||
# ```coffee
|
||||
# {BufferedProcess} = require 'nylas-exports'
|
||||
#
|
||||
# command = 'ps'
|
||||
# args = ['-ef']
|
||||
# stdout = (output) -> console.log(output)
|
||||
# exit = (code) -> console.log("ps -ef exited with #{code}")
|
||||
# process = new BufferedProcess({command, args, stdout, exit})
|
||||
# ```
|
||||
module.exports =
|
||||
class BufferedProcess
|
||||
###
|
||||
Section: Construction
|
||||
###
|
||||
|
||||
# Public: Runs the given command by spawning a new child process.
|
||||
#
|
||||
# * `options` An {Object} with the following keys:
|
||||
# * `command` The {String} command to execute.
|
||||
# * `args` The {Array} of arguments to pass to the command (optional).
|
||||
# * `options` {Object} (optional) The options {Object} to pass to Node's
|
||||
# `ChildProcess.spawn` method.
|
||||
# * `stdout` {Function} (optional) The callback that receives a single
|
||||
# argument which contains the standard output from the command. The
|
||||
# callback is called as data is received but it's buffered to ensure only
|
||||
# complete lines are passed until the source stream closes. After the
|
||||
# source stream has closed all remaining data is sent in a final call.
|
||||
# * `data` {String}
|
||||
# * `stderr` {Function} (optional) The callback that receives a single
|
||||
# argument which contains the standard error output from the command. The
|
||||
# callback is called as data is received but it's buffered to ensure only
|
||||
# complete lines are passed until the source stream closes. After the
|
||||
# source stream has closed all remaining data is sent in a final call.
|
||||
# * `data` {String}
|
||||
# * `exit` {Function} (optional) The callback which receives a single
|
||||
# argument containing the exit status.
|
||||
# * `code` {Number}
|
||||
constructor: ({command, args, options, stdout, stderr, exit}={}) ->
|
||||
@emitter = new Emitter
|
||||
options ?= {}
|
||||
@command = command
|
||||
# Related to joyent/node#2318
|
||||
if process.platform is 'win32'
|
||||
# Quote all arguments and escapes inner quotes
|
||||
if args?
|
||||
cmdArgs = args.filter (arg) -> arg?
|
||||
cmdArgs = cmdArgs.map (arg) =>
|
||||
if @isExplorerCommand(command) and /^\/[a-zA-Z]+,.*$/.test(arg)
|
||||
# Don't wrap /root,C:\folder style arguments to explorer calls in
|
||||
# quotes since they will not be interpreted correctly if they are
|
||||
arg
|
||||
else
|
||||
"\"#{arg.toString().replace(/"/g, '\\"')}\""
|
||||
else
|
||||
cmdArgs = []
|
||||
if /\s/.test(command)
|
||||
cmdArgs.unshift("\"#{command}\"")
|
||||
else
|
||||
cmdArgs.unshift(command)
|
||||
cmdArgs = ['/s', '/c', "\"#{cmdArgs.join(' ')}\""]
|
||||
cmdOptions = _.clone(options)
|
||||
cmdOptions.windowsVerbatimArguments = true
|
||||
@spawn(@getCmdPath(), cmdArgs, cmdOptions)
|
||||
else
|
||||
@spawn(command, args, options)
|
||||
|
||||
@killed = false
|
||||
@handleEvents(stdout, stderr, exit)
|
||||
|
||||
###
|
||||
Section: Event Subscription
|
||||
###
|
||||
|
||||
# Public: Will call your callback when an error will be raised by the process.
|
||||
# Usually this is due to the command not being available or not on the PATH.
|
||||
# You can call `handle()` on the object passed to your callback to indicate
|
||||
# that you have handled this error.
|
||||
#
|
||||
# * `callback` {Function} callback
|
||||
# * `errorObject` {Object}
|
||||
# * `error` {Object} the error object
|
||||
# * `handle` {Function} call this to indicate you have handled the error.
|
||||
# The error will not be thrown if this function is called.
|
||||
#
|
||||
# Returns a {Disposable}
|
||||
onWillThrowError: (callback) ->
|
||||
@emitter.on 'will-throw-error', callback
|
||||
|
||||
###
|
||||
Section: Helper Methods
|
||||
###
|
||||
|
||||
# Helper method to pass data line by line.
|
||||
#
|
||||
# * `stream` The Stream to read from.
|
||||
# * `onLines` The callback to call with each line of data.
|
||||
# * `onDone` The callback to call when the stream has closed.
|
||||
bufferStream: (stream, onLines, onDone) ->
|
||||
stream.setEncoding('utf8')
|
||||
buffered = ''
|
||||
|
||||
stream.on 'data', (data) =>
|
||||
return if @killed
|
||||
buffered += data
|
||||
lastNewlineIndex = buffered.lastIndexOf('\n')
|
||||
if lastNewlineIndex isnt -1
|
||||
onLines(buffered.substring(0, lastNewlineIndex + 1))
|
||||
buffered = buffered.substring(lastNewlineIndex + 1)
|
||||
|
||||
stream.on 'close', =>
|
||||
return if @killed
|
||||
onLines(buffered) if buffered.length > 0
|
||||
onDone()
|
||||
|
||||
# Kill all child processes of the spawned cmd.exe process on Windows.
|
||||
#
|
||||
# This is required since killing the cmd.exe does not terminate child
|
||||
# processes.
|
||||
killOnWindows: ->
|
||||
return unless @process?
|
||||
|
||||
parentPid = @process.pid
|
||||
cmd = 'wmic'
|
||||
args = [
|
||||
'process'
|
||||
'where'
|
||||
"(ParentProcessId=#{parentPid})"
|
||||
'get'
|
||||
'processid'
|
||||
]
|
||||
|
||||
try
|
||||
wmicProcess = ChildProcess.spawn(cmd, args)
|
||||
catch spawnError
|
||||
@killProcess()
|
||||
return
|
||||
|
||||
wmicProcess.on 'error', -> # ignore errors
|
||||
output = ''
|
||||
wmicProcess.stdout.on 'data', (data) -> output += data
|
||||
wmicProcess.stdout.on 'close', =>
|
||||
pidsToKill = output.split(/\s+/)
|
||||
.filter (pid) -> /^\d+$/.test(pid)
|
||||
.map (pid) -> parseInt(pid)
|
||||
.filter (pid) -> pid isnt parentPid and 0 < pid < Infinity
|
||||
|
||||
for pid in pidsToKill
|
||||
try
|
||||
process.kill(pid)
|
||||
@killProcess()
|
||||
|
||||
killProcess: ->
|
||||
@process?.kill()
|
||||
@process = null
|
||||
|
||||
isExplorerCommand: (command) ->
|
||||
if command is 'explorer.exe' or command is 'explorer'
|
||||
true
|
||||
else if process.env.SystemRoot
|
||||
command is path.join(process.env.SystemRoot, 'explorer.exe') or command is path.join(process.env.SystemRoot, 'explorer')
|
||||
else
|
||||
false
|
||||
|
||||
getCmdPath: ->
|
||||
if process.env.comspec
|
||||
process.env.comspec
|
||||
else if process.env.SystemRoot
|
||||
path.join(process.env.SystemRoot, 'System32', 'cmd.exe')
|
||||
else
|
||||
'cmd.exe'
|
||||
|
||||
# Public: Terminate the process.
|
||||
kill: ->
|
||||
return if @killed
|
||||
|
||||
@killed = true
|
||||
if process.platform is 'win32'
|
||||
@killOnWindows()
|
||||
else
|
||||
@killProcess()
|
||||
|
||||
undefined
|
||||
|
||||
spawn: (command, args, options) ->
|
||||
try
|
||||
@process = ChildProcess.spawn(command, args, options)
|
||||
catch spawnError
|
||||
setTimeout((=> @handleError(spawnError)), 0)
|
||||
|
||||
handleEvents: (stdout, stderr, exit) ->
|
||||
return unless @process?
|
||||
|
||||
stdoutClosed = true
|
||||
stderrClosed = true
|
||||
processExited = true
|
||||
exitCode = 0
|
||||
triggerExitCallback = ->
|
||||
return if @killed
|
||||
if stdoutClosed and stderrClosed and processExited
|
||||
exit?(exitCode)
|
||||
|
||||
if stdout
|
||||
stdoutClosed = false
|
||||
@bufferStream @process.stdout, stdout, ->
|
||||
stdoutClosed = true
|
||||
triggerExitCallback()
|
||||
|
||||
if stderr
|
||||
stderrClosed = false
|
||||
@bufferStream @process.stderr, stderr, ->
|
||||
stderrClosed = true
|
||||
triggerExitCallback()
|
||||
|
||||
if exit
|
||||
processExited = false
|
||||
@process.on 'exit', (code) ->
|
||||
exitCode = code
|
||||
processExited = true
|
||||
triggerExitCallback()
|
||||
|
||||
@process.on 'error', (error) => @handleError(error)
|
||||
return
|
||||
|
||||
handleError: (error) ->
|
||||
handled = false
|
||||
handle = -> handled = true
|
||||
|
||||
@emitter.emit 'will-throw-error', {error, handle}
|
||||
|
||||
if error.code is 'ENOENT' and error.syscall.indexOf('spawn') is 0
|
||||
error = new Error("Failed to spawn command `#{@command}`. Make sure `#{@command}` is installed and on your PATH", error.path)
|
||||
error.name = 'BufferedProcessError'
|
||||
|
||||
throw error unless handled
|
|
@ -1,89 +0,0 @@
|
|||
_ = require 'underscore'
|
||||
ParsedColor = null
|
||||
|
||||
# Essential: A simple color class returned from {Config::get} when the value
|
||||
# at the key path is of type 'color'.
|
||||
module.exports =
|
||||
class Color
|
||||
# Essential: Parse a {String} or {Object} into a {Color}.
|
||||
#
|
||||
# * `value` A {String} such as `'white'`, `#ff00ff`, or
|
||||
# `'rgba(255, 15, 60, .75)'` or an {Object} with `red`, `green`, `blue`,
|
||||
# and `alpha` properties.
|
||||
#
|
||||
# Returns a {Color} or `null` if it cannot be parsed.
|
||||
@parse: (value) ->
|
||||
return null if _.isArray(value) or _.isFunction(value)
|
||||
return null unless _.isObject(value) or _.isString(value)
|
||||
|
||||
ParsedColor ?= require 'color'
|
||||
|
||||
try
|
||||
parsedColor = new ParsedColor(value)
|
||||
catch error
|
||||
return null
|
||||
|
||||
new Color(parsedColor.red(), parsedColor.green(), parsedColor.blue(), parsedColor.alpha())
|
||||
|
||||
constructor: (red, green, blue, alpha) ->
|
||||
Object.defineProperties this,
|
||||
red:
|
||||
set: (newRed) -> red = parseColor(newRed)
|
||||
get: -> red
|
||||
enumerable: true
|
||||
configurable: false
|
||||
green:
|
||||
set: (newGreen) -> green = parseColor(newGreen)
|
||||
get: -> green
|
||||
enumerable: true
|
||||
configurable: false
|
||||
blue:
|
||||
set: (newBlue) -> blue = parseColor(newBlue)
|
||||
get: -> blue
|
||||
enumerable: true
|
||||
configurable: false
|
||||
alpha:
|
||||
set: (newAlpha) -> alpha = parseAlpha(newAlpha)
|
||||
get: -> alpha
|
||||
enumerable: true
|
||||
configurable: false
|
||||
|
||||
@red = red
|
||||
@green = green
|
||||
@blue = blue
|
||||
@alpha = alpha
|
||||
|
||||
# Essential: Returns a {String} in the form `'#abcdef'`.
|
||||
toHexString: ->
|
||||
"##{numberToHexString(@red)}#{numberToHexString(@green)}#{numberToHexString(@blue)}"
|
||||
|
||||
# Essential: Returns a {String} in the form `'rgba(25, 50, 75, .9)'`.
|
||||
toRGBAString: ->
|
||||
"rgba(#{@red}, #{@green}, #{@blue}, #{@alpha})"
|
||||
|
||||
isEqual: (color) ->
|
||||
return true if this is color
|
||||
color = Color.parse(color) unless color instanceof Color
|
||||
return false unless color?
|
||||
color.red is @red and color.blue is @blue and color.green is @green and color.alpha is @alpha
|
||||
|
||||
clone: -> new Color(@red, @green, @blue, @alpha)
|
||||
|
||||
parseColor = (color) ->
|
||||
color = parseInt(color)
|
||||
color = 0 if isNaN(color)
|
||||
color = Math.max(color, 0)
|
||||
color = Math.min(color, 255)
|
||||
color
|
||||
|
||||
parseAlpha = (alpha) ->
|
||||
alpha = parseFloat(alpha)
|
||||
alpha = 1 if isNaN(alpha)
|
||||
alpha = Math.max(alpha, 0)
|
||||
alpha = Math.min(alpha, 1)
|
||||
alpha
|
||||
|
||||
numberToHexString = (number) ->
|
||||
hex = number.toString(16)
|
||||
hex = "0#{hex}" if number < 10
|
||||
hex
|
|
@ -5,8 +5,6 @@ fs = require 'fs-plus'
|
|||
EmitterMixin = require('emissary').Emitter
|
||||
{CompositeDisposable, Disposable, Emitter} = require 'event-kit'
|
||||
|
||||
Color = require './color'
|
||||
|
||||
if process.type is 'renderer'
|
||||
app = remote.getGlobal('application')
|
||||
webContentsId = remote.getCurrentWebContents().getId()
|
||||
|
@ -218,21 +216,6 @@ else
|
|||
# maximum: 11.5
|
||||
# ```
|
||||
#
|
||||
# #### color
|
||||
#
|
||||
# Values will be coerced into a {Color} with `red`, `green`, `blue`, and `alpha`
|
||||
# properties that all have numeric values. `red`, `green`, `blue` will be in
|
||||
# the range 0 to 255 and `value` will be in the range 0 to 1. Values can be any
|
||||
# valid CSS color format such as `#abc`, `#abcdef`, `white`,
|
||||
# `rgb(50, 100, 150)`, and `rgba(25, 75, 125, .75)`.
|
||||
#
|
||||
# ```coffee
|
||||
# config:
|
||||
# someSetting:
|
||||
# type: 'color'
|
||||
# default: 'white'
|
||||
# ```
|
||||
#
|
||||
# ### Other Supported Keys
|
||||
#
|
||||
# #### enum
|
||||
|
@ -593,9 +576,7 @@ class Config
|
|||
@_logError("Failed to set keypath to default: #{keyPath} = #{JSON.stringify(defaults)}", e)
|
||||
|
||||
deepClone: (object) ->
|
||||
if object instanceof Color
|
||||
object.clone()
|
||||
else if _.isArray(object)
|
||||
if _.isArray(object)
|
||||
object.map (value) => @deepClone(value)
|
||||
else if isPlainObject(object)
|
||||
_.mapObject object, (value) => @deepClone(value)
|
||||
|
@ -725,13 +706,6 @@ Config.addSchemaEnforcers
|
|||
else
|
||||
value
|
||||
|
||||
'color':
|
||||
coerce: (keyPath, value, schema) ->
|
||||
color = Color.parse(value)
|
||||
unless color?
|
||||
throw new Error("Validation failed at #{keyPath}, #{JSON.stringify(value)} cannot be coerced into a color")
|
||||
color
|
||||
|
||||
'*':
|
||||
coerceMinimumAndMaximum: (keyPath, value, schema) ->
|
||||
return value unless typeof value is 'number'
|
||||
|
@ -752,7 +726,7 @@ Config.addSchemaEnforcers
|
|||
throw new Error("Validation failed at #{keyPath}, #{JSON.stringify(value)} is not one of #{JSON.stringify(possibleValues)}")
|
||||
|
||||
isPlainObject = (value) ->
|
||||
_.isObject(value) and not _.isArray(value) and not _.isFunction(value) and not _.isString(value) and not (value instanceof Color)
|
||||
_.isObject(value) and not _.isArray(value) and not _.isFunction(value) and not _.isString(value)
|
||||
|
||||
splitKeyPath = (keyPath) ->
|
||||
return [] unless keyPath?
|
||||
|
|
|
@ -14,8 +14,6 @@
|
|||
// Extend the standard promise class a bit
|
||||
import './promise-extensions';
|
||||
|
||||
import './window';
|
||||
|
||||
import NylasEnvConstructor from './nylas-env';
|
||||
window.NylasEnv = window.atom = NylasEnvConstructor.loadOrCreate();
|
||||
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
{userAgent, taskPath} = process.env
|
||||
handler = null
|
||||
|
||||
setupGlobals = ->
|
||||
global.attachEvent = ->
|
||||
console =
|
||||
warn: -> emit 'task:warn', arguments...
|
||||
log: -> emit 'task:log', arguments...
|
||||
error: -> emit 'task:error', arguments...
|
||||
trace: ->
|
||||
global.__defineGetter__ 'console', -> console
|
||||
|
||||
global.document =
|
||||
createElement: ->
|
||||
setAttribute: ->
|
||||
getElementsByTagName: -> []
|
||||
appendChild: ->
|
||||
documentElement:
|
||||
insertBefore: ->
|
||||
removeChild: ->
|
||||
getElementById: -> {}
|
||||
createComment: -> {}
|
||||
createDocumentFragment: -> {}
|
||||
|
||||
global.emit = (event, args...) ->
|
||||
process.send({event, args})
|
||||
global.navigator = {userAgent}
|
||||
global.window = global
|
||||
|
||||
handleEvents = ->
|
||||
process.on 'unhandledRejection', (reason, promise) ->
|
||||
console.error(reason.stack, promise)
|
||||
process.on 'uncaughtException', (error) ->
|
||||
console.error(error.message, error.stack)
|
||||
process.on 'message', ({event, args}={}) ->
|
||||
return unless event is 'start'
|
||||
|
||||
isAsync = false
|
||||
async = ->
|
||||
isAsync = true
|
||||
(result) ->
|
||||
emit('task:completed', result)
|
||||
result = handler.bind({async})(args...)
|
||||
emit('task:completed', result) unless isAsync
|
||||
|
||||
setupGlobals()
|
||||
handleEvents()
|
||||
handler = require(taskPath)
|
|
@ -1,167 +0,0 @@
|
|||
_ = require 'underscore'
|
||||
ChildProcess = require 'child_process'
|
||||
{Emitter} = require 'event-kit'
|
||||
|
||||
# Extended: Run a node script in a separate process.
|
||||
#
|
||||
# Used by the fuzzy-finder and [find in project](https://github.com/atom/atom/blob/master/src/scan-handler.coffee).
|
||||
#
|
||||
# For a real-world example, see the [scan-handler](https://github.com/atom/atom/blob/master/src/scan-handler.coffee)
|
||||
# and the [instantiation of the task](https://github.com/atom/atom/blob/4a20f13162f65afc816b512ad7201e528c3443d7/src/project.coffee#L245).
|
||||
#
|
||||
# ## Examples
|
||||
#
|
||||
# In your package code:
|
||||
#
|
||||
# ```coffee
|
||||
# {Task} = require './task'
|
||||
#
|
||||
# task = Task.once '/path/to/task-file.coffee', parameter1, parameter2, ->
|
||||
# console.log 'task has finished'
|
||||
#
|
||||
# task.on 'some-event-from-the-task', (data) =>
|
||||
# console.log data.someString # prints 'yep this is it'
|
||||
# ```
|
||||
#
|
||||
# In `'/path/to/task-file.coffee'`:
|
||||
#
|
||||
# ```coffee
|
||||
# module.exports = (parameter1, parameter2) ->
|
||||
# # Indicates that this task will be async.
|
||||
# # Call the `callback` to finish the task
|
||||
# callback = @async()
|
||||
#
|
||||
# emit('some-event-from-the-task', {someString: 'yep this is it'})
|
||||
#
|
||||
# callback()
|
||||
# ```
|
||||
module.exports =
|
||||
class Task
|
||||
# Public: A helper method to easily launch and run a task once.
|
||||
#
|
||||
# * `taskPath` The {String} path to the CoffeeScript/JavaScript file which
|
||||
# exports a single {Function} to execute.
|
||||
# * `args` The arguments to pass to the exported function.
|
||||
#
|
||||
# Returns the created {Task}.
|
||||
@once: (taskPath, args...) ->
|
||||
task = new Task(taskPath)
|
||||
task.once 'task:completed', -> task.terminate()
|
||||
task.start(args...)
|
||||
task
|
||||
|
||||
# Called upon task completion.
|
||||
#
|
||||
# It receives the same arguments that were passed to the task.
|
||||
#
|
||||
# If subclassed, this is intended to be overridden. However if {::start}
|
||||
# receives a completion callback, this is overridden.
|
||||
callback: null
|
||||
|
||||
# Public: Creates a task. You should probably use {.once}
|
||||
#
|
||||
# * `taskPath` The {String} path to the CoffeeScript/JavaScript file that
|
||||
# exports a single {Function} to execute.
|
||||
constructor: (taskPath) ->
|
||||
@emitter = new Emitter
|
||||
|
||||
compileCacheRequire = "require('#{require.resolve('./compile-cache')}')"
|
||||
compileCachePath = require('./compile-cache').getCacheDirectory()
|
||||
taskBootstrapRequire = "require('#{require.resolve('./task-bootstrap')}');"
|
||||
bootstrap = """
|
||||
#{compileCacheRequire}.setCacheDirectory('#{compileCachePath}');
|
||||
#{taskBootstrapRequire}
|
||||
"""
|
||||
bootstrap = bootstrap.replace(/\\/g, "\\\\")
|
||||
|
||||
taskPath = require.resolve(taskPath)
|
||||
taskPath = taskPath.replace(/\\/g, "\\\\")
|
||||
|
||||
env = Object.assign({}, process.env, {taskPath, userAgent: 'NylasMail'})
|
||||
@childProcess = ChildProcess.fork '--eval', [bootstrap], {env, silent: true}
|
||||
|
||||
@on "task:log", -> console.log(arguments...)
|
||||
@on "task:warn", -> console.warn(arguments...)
|
||||
@on "task:error", -> console.error(arguments...)
|
||||
@on "task:completed", (args...) => @callback?(args...)
|
||||
|
||||
@handleEvents()
|
||||
|
||||
# Routes messages from the child to the appropriate event.
|
||||
handleEvents: ->
|
||||
@childProcess.removeAllListeners()
|
||||
@childProcess.on 'message', ({event, args}) =>
|
||||
@emitter.emit(event, args) if @childProcess?
|
||||
|
||||
# Catch the errors that happened before task-bootstrap.
|
||||
if @childProcess.stdout?
|
||||
@childProcess.stdout.removeAllListeners()
|
||||
@childProcess.stdout.on 'data', (data) -> console.log data.toString()
|
||||
|
||||
if @childProcess.stderr?
|
||||
@childProcess.stderr.removeAllListeners()
|
||||
@childProcess.stderr.on 'data', (data) -> console.error data.toString()
|
||||
|
||||
# Public: Starts the task.
|
||||
#
|
||||
# Throws an error if this task has already been terminated or if sending a
|
||||
# message to the child process fails.
|
||||
#
|
||||
# * `args` The arguments to pass to the function exported by this task's script.
|
||||
# * `callback` (optional) A {Function} to call when the task completes.
|
||||
start: (args..., callback) ->
|
||||
throw new Error('Cannot start terminated process') unless @childProcess?
|
||||
|
||||
@handleEvents()
|
||||
if _.isFunction(callback)
|
||||
@callback = callback
|
||||
else
|
||||
args.push(callback)
|
||||
@send({event: 'start', args})
|
||||
undefined
|
||||
|
||||
# Public: Send message to the task.
|
||||
#
|
||||
# Throws an error if this task has already been terminated or if sending a
|
||||
# message to the child process fails.
|
||||
#
|
||||
# * `message` The message to send to the task.
|
||||
send: (message) ->
|
||||
if @childProcess?
|
||||
@childProcess.send(message)
|
||||
else
|
||||
throw new Error('Cannot send message to terminated process')
|
||||
undefined
|
||||
|
||||
# Public: Call a function when an event is emitted by the child process
|
||||
#
|
||||
# * `eventName` The {String} name of the event to handle.
|
||||
# * `callback` The {Function} to call when the event is emitted.
|
||||
#
|
||||
# Returns a {Disposable} that can be used to stop listening for the event.
|
||||
on: (eventName, callback) -> @emitter.on eventName, (args) -> callback(args...)
|
||||
|
||||
once: (eventName, callback) ->
|
||||
disposable = @on eventName, (args...) ->
|
||||
disposable.dispose()
|
||||
callback(args...)
|
||||
|
||||
# Public: Forcefully stop the running task.
|
||||
#
|
||||
# No more events are emitted once this method is called.
|
||||
terminate: ->
|
||||
return false unless @childProcess?
|
||||
|
||||
@childProcess.removeAllListeners()
|
||||
@childProcess.stdout?.removeAllListeners()
|
||||
@childProcess.stderr?.removeAllListeners()
|
||||
@childProcess.kill()
|
||||
@childProcess = null
|
||||
|
||||
true
|
||||
|
||||
cancel: ->
|
||||
didForcefullyTerminate = @terminate()
|
||||
if didForcefullyTerminate
|
||||
@emitter.emit('task:canceled')
|
||||
didForcefullyTerminate
|
|
@ -3,9 +3,6 @@
|
|||
// Extend the standard promise class a bit
|
||||
import './promise-extensions';
|
||||
|
||||
// Like sands through the hourglass, so are the days of our lives.
|
||||
import './window';
|
||||
|
||||
import NylasEnvConstructor from './nylas-env';
|
||||
window.NylasEnv = NylasEnvConstructor.loadOrCreate();
|
||||
NylasEnv.initialize();
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
# Public: Measure how long a function takes to run.
|
||||
#
|
||||
# description - A {String} description that will be logged to the console when
|
||||
# the function completes.
|
||||
# fn - A {Function} to measure the duration of.
|
||||
#
|
||||
# Returns the value returned by the given function.
|
||||
window.measure = (description, fn) ->
|
||||
start = Date.now()
|
||||
value = fn()
|
||||
result = Date.now() - start
|
||||
console.log description, result
|
||||
value
|
||||
|
||||
# Public: Create a dev tools profile for a function.
|
||||
#
|
||||
# description - A {String} description that will be available in the Profiles
|
||||
# tab of the dev tools.
|
||||
# fn - A {Function} to profile.
|
||||
#
|
||||
# Returns the value returned by the given function.
|
||||
window.profile = (description, fn) ->
|
||||
measure description, ->
|
||||
console.profile(description)
|
||||
value = fn()
|
||||
console.profileEnd(description)
|
||||
value
|
Loading…
Reference in a new issue