Separate the device state updating into its own module.

This commit is contained in:
Pagan Gazzard 2015-05-12 16:59:57 +01:00 committed by Pablo Carranza Vélez
parent 4fa84b1108
commit 0917a46a2d
3 changed files with 87 additions and 79 deletions

View File

@ -31,6 +31,7 @@ knex.init.then ->
api = require './api'
application = require './application'
{ updateDeviceState } = require './state'
randomstring = require 'randomstring'
console.log('Starting API server..')
@ -39,7 +40,7 @@ knex.init.then ->
# Let API know what version we are, and our api connection info.
console.log('Updating supervisor version and api info')
application.updateDeviceState(
updateDeviceState(
api_port: config.listenPort
api_secret: secret
supervisor_version: utils.supervisorVersion
@ -63,7 +64,7 @@ knex.init.then ->
updateIpAddr = ->
utils.findIpAddrs().then (ipAddrs) ->
application.updateDeviceState(
updateDeviceState(
ip_address: ipAddrs.join(' ')
)
console.log('Starting periodic check for IP addresses..')

View File

@ -8,7 +8,8 @@ Promise = require 'bluebird'
utils = require './utils'
tty = require './lib/tty'
logger = require './lib/logger'
{ resinApi, cachedResinApi } = require './request'
{ cachedResinApi } = require './request'
{ getDeviceID, updateDeviceState } = require './state'
{ docker } = dockerUtils
@ -357,79 +358,3 @@ exports.update = update = ->
.finally ->
# Set the updating as finished in its own block, so it never has to worry about other code stopping this.
currentlyUpdating = 0
getDeviceID = do ->
deviceIdPromise = null
return ->
# We initialise the rejected promise just before we catch in order to avoid a useless first unhandled error warning.
deviceIdPromise ?= Promise.rejected()
# Only fetch the device id once (when successful, otherwise retry for each request)
deviceIdPromise = deviceIdPromise.catch ->
Promise.all([
knex('config').select('value').where(key: 'apiKey')
knex('config').select('value').where(key: 'uuid')
])
.spread ([{value: apiKey}], [{value: uuid}]) ->
resinApi.get(
resource: 'device'
options:
select: 'id'
filter:
uuid: uuid
customOptions:
apikey: apiKey
)
.then (devices) ->
if devices.length is 0
throw new Error('Could not find this device?!')
return devices[0].id
# Calling this function updates the local device state, which is then used to synchronise
# the remote device state, repeating any failed updates until successfully synchronised.
# This function will also optimise updates by merging multiple updates and only sending the latest state.
exports.updateDeviceState = updateDeviceState = do ->
applyPromise = Promise.resolve()
targetState = {}
actualState = {}
getStateDiff = ->
_.omit targetState, (value, key) ->
actualState[key] is value
applyState = ->
if _.size(getStateDiff()) is 0
return
applyPromise = Promise.join(
knex('config').select('value').where(key: 'apiKey')
getDeviceID()
([{value: apiKey}], deviceID) ->
stateDiff = getStateDiff()
if _.size(stateDiff) is 0
return
resinApi.patch
resource: 'device'
id: deviceID
body: stateDiff
customOptions:
apikey: apiKey
.then ->
# Update the actual state.
_.merge(actualState, stateDiff)
.catch (error) ->
utils.mixpanelTrack('Device info update failure', {error, stateDiff})
# Delay 5s before retrying a failed update
Promise.delay(5000)
)
.finally ->
# Check if any more state diffs have appeared whilst we've been processing this update.
applyState()
return (updatedState = {}, retry = false) ->
# Remove any updates that match the last we successfully sent.
_.merge(targetState, updatedState)
# Only trigger applying state if an apply isn't already in progress.
if !applyPromise.isPending()
applyState()
return

82
src/state.coffee Normal file
View File

@ -0,0 +1,82 @@
_ = require 'lodash'
Promise = require 'bluebird'
knex = require './db'
utils = require './utils'
{ resinApi } = require './request'
exports.getDeviceID = getDeviceID = do ->
deviceIdPromise = null
return ->
# We initialise the rejected promise just before we catch in order to avoid a useless first unhandled error warning.
deviceIdPromise ?= Promise.rejected()
# Only fetch the device id once (when successful, otherwise retry for each request)
deviceIdPromise = deviceIdPromise.catch ->
Promise.all([
knex('config').select('value').where(key: 'apiKey')
knex('config').select('value').where(key: 'uuid')
])
.spread ([{value: apiKey}], [{value: uuid}]) ->
resinApi.get(
resource: 'device'
options:
select: 'id'
filter:
uuid: uuid
customOptions:
apikey: apiKey
)
.then (devices) ->
if devices.length is 0
throw new Error('Could not find this device?!')
return devices[0].id
# Calling this function updates the local device state, which is then used to synchronise
# the remote device state, repeating any failed updates until successfully synchronised.
# This function will also optimise updates by merging multiple updates and only sending the latest state.
exports.updateDeviceState = updateDeviceState = do ->
applyPromise = Promise.resolve()
targetState = {}
actualState = {}
getStateDiff = ->
_.omit targetState, (value, key) ->
actualState[key] is value
applyState = ->
if _.size(getStateDiff()) is 0
return
applyPromise = Promise.join(
knex('config').select('value').where(key: 'apiKey')
getDeviceID()
([{value: apiKey}], deviceID) ->
stateDiff = getStateDiff()
if _.size(stateDiff) is 0
return
resinApi.patch
resource: 'device'
id: deviceID
body: stateDiff
customOptions:
apikey: apiKey
.then ->
# Update the actual state.
_.merge(actualState, stateDiff)
.catch (error) ->
utils.mixpanelTrack('Device info update failure', {error, stateDiff})
# Delay 5s before retrying a failed update
Promise.delay(5000)
)
.finally ->
# Check if any more state diffs have appeared whilst we've been processing this update.
applyState()
return (updatedState = {}, retry = false) ->
# Remove any updates that match the last we successfully sent.
_.merge(targetState, updatedState)
# Only trigger applying state if an apply isn't already in progress.
if !applyPromise.isPending()
applyState()
return