JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
basic features for updating/checking one instance
[af-coffee.git] / client.coffee
diff --git a/client.coffee b/client.coffee
new file mode 100644 (file)
index 0000000..a378480
--- /dev/null
@@ -0,0 +1,76 @@
+fs = require 'fs'
+async = require 'async'
+af = require './api.coffee'
+
+token_file = "#{process.env.HOME}/.af-coffee-token"
+
+timeout = (ms, f) -> setTimeout f, ms
+
+# a session caches the api access token and prompts for the username and
+# password if it goes stale. It re-tries API calls that fail due to
+# invalid/expired token
+
+class Session
+       constructor: ->
+               @token = 0
+
+       api: (call, args..., callback) ->
+               async.waterfall [
+                       (callback) =>
+                               switch @token
+                                       when 0
+                                               get_token callback
+                                       when -1
+                                               login callback
+                                       else
+                                               callback null, @token
+                       (token, callback) =>
+                               @token = token
+                               af[call] @token, args..., callback
+               ], (err, result) =>
+                       # eg /app/xxx/stats sometimes returns 404 with wrong auth token
+                       if err?.code is 403 or err?.code is 404
+                               @token = -1
+                               @api(call, args..., callback)
+                       else
+                               callback err, result
+                               
+ask = (opts, callback) ->
+       process.stdout.write opts.prompt
+       process.stdin.setEncoding 'utf8'
+       process.stdin.resume()
+       process.stdin.once 'data', (line) ->
+               if opts.silent
+                       # send ^[[A^[[2K to move the cursor up one line, then clear that line
+                       process.stdout.write new Buffer [27, 91, 65, 27, 91, 50, 75]
+                       process.stdout.write opts.prompt + "***\n"
+               process.stdin.pause()
+               callback null, (line.substr 0, line.length - 1)
+
+get_token = (callback) ->
+       fs.readFile token_file, 'utf8', (err, token) ->
+               if err?
+                       login callback
+               else
+                       callback null, token
+
+login = (callback) ->
+       async.waterfall [
+               (callback) -> async.series [
+                       (callback) -> ask prompt: 'username: ', callback
+                       (callback) -> ask prompt: 'password: ', silent: true, callback
+               ], callback
+               ([username, password], callback) ->
+                       af.login username, password, callback
+               (token, callback) ->
+                       # wait for file write so there's no race condition if get_token gets called soon
+                       fs.writeFile token_file, token, (err) ->
+                               if err
+                                       console.log "Warning: couldn't cache auth token in #{token_file}: ", err
+                               # don't pass on error, it's ok if we can't cache it
+                               callback null, token
+       ], callback
+
+
+exports.new_session = ->
+       return new Session()