a4tools 1.2.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.bundle/install.log +38 -0
- data/.gitignore +2 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +38 -0
- data/a4tools.gemspec +38 -0
- data/bin/deploy_latest_clients +32 -0
- data/bin/devsite_config_server +48 -0
- data/bin/netshell +23 -0
- data/bin/update_server +101 -0
- data/bin/usher +54 -0
- data/lib/a4tools.rb +61 -0
- data/lib/a4tools/version.rb +3 -0
- data/lib/acres_client.rb +376 -0
- data/lib/clients/caching_client.rb +151 -0
- data/lib/clients/deployment_client.rb +53 -0
- data/lib/clients/kai_config_client.rb +39 -0
- data/lib/clients/usher_client.rb +72 -0
- data/lib/clients/usher_mgmt_client.rb +201 -0
- data/lib/event_manager.rb +24 -0
- data/lib/events.json +1 -0
- data/lib/net_shell/builtin_command.rb +312 -0
- data/lib/net_shell/builtin_commands/build.rb +251 -0
- data/lib/net_shell/builtin_commands/cd.rb +12 -0
- data/lib/net_shell/builtin_commands/connect.rb +122 -0
- data/lib/net_shell/builtin_commands/deploy.rb +280 -0
- data/lib/net_shell/builtin_commands/disconnect.rb +15 -0
- data/lib/net_shell/builtin_commands/excerpt.rb +97 -0
- data/lib/net_shell/builtin_commands/exit.rb +7 -0
- data/lib/net_shell/builtin_commands/get.rb +38 -0
- data/lib/net_shell/builtin_commands/help.rb +40 -0
- data/lib/net_shell/builtin_commands/host.rb +126 -0
- data/lib/net_shell/builtin_commands/inject.rb +42 -0
- data/lib/net_shell/builtin_commands/jsoncache.rb +80 -0
- data/lib/net_shell/builtin_commands/kai_event.rb +151 -0
- data/lib/net_shell/builtin_commands/persist.rb +24 -0
- data/lib/net_shell/builtin_commands/pwd.rb +6 -0
- data/lib/net_shell/builtin_commands/recap.rb +188 -0
- data/lib/net_shell/builtin_commands/references.rb +63 -0
- data/lib/net_shell/builtin_commands/select.rb +36 -0
- data/lib/net_shell/builtin_commands/send.rb +74 -0
- data/lib/net_shell/builtin_commands/set.rb +29 -0
- data/lib/net_shell/builtin_commands/show.rb +183 -0
- data/lib/net_shell/builtin_commands/site.rb +122 -0
- data/lib/net_shell/builtin_commands/ssh.rb +62 -0
- data/lib/net_shell/builtin_commands/talk.rb +90 -0
- data/lib/net_shell/builtin_commands/translate.rb +45 -0
- data/lib/net_shell/builtin_commands/unset.rb +14 -0
- data/lib/net_shell/builtin_commands/usher.rb +55 -0
- data/lib/net_shell/builtin_commands/usher_device.rb +39 -0
- data/lib/net_shell/builtin_commands/usher_site.rb +245 -0
- data/lib/net_shell/builtin_commands/usherm_connect.rb +21 -0
- data/lib/net_shell/colors.rb +149 -0
- data/lib/net_shell/command.rb +97 -0
- data/lib/net_shell/io.rb +132 -0
- data/lib/net_shell/net_shell.rb +396 -0
- data/lib/net_shell/prompt.rb +335 -0
- data/lib/object_builder/definitions/app_info_for_script.rb +83 -0
- data/lib/object_builder/definitions/connection_request.rb +28 -0
- data/lib/object_builder/definitions/device_info_for_system.rb +37 -0
- data/lib/object_builder/object_builder.rb +145 -0
- data/lib/talk.json +1 -0
- data/lib/talk_consumer.rb +235 -0
- metadata +279 -0
data/lib/acres_client.rb
ADDED
@@ -0,0 +1,376 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'uri'
|
3
|
+
require 'net/https'
|
4
|
+
require 'faye/websocket'
|
5
|
+
require 'eventmachine'
|
6
|
+
require 'thread'
|
7
|
+
|
8
|
+
|
9
|
+
module A4Tools
|
10
|
+
module EventGenerator
|
11
|
+
def on(key, &callback)
|
12
|
+
@callbacks ||= {}
|
13
|
+
@callbacks[key] ||= []
|
14
|
+
@callbacks[key].push callback
|
15
|
+
end
|
16
|
+
|
17
|
+
def signal(key, value=nil)
|
18
|
+
return if @callbacks.nil?
|
19
|
+
[ key, :all ].each do |k|
|
20
|
+
next unless @callbacks.has_key? k
|
21
|
+
@callbacks[k].each { |callback| callback.call(key, value) }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def passthrough(target)
|
26
|
+
target.on(:all) { |trigger, value| signal(trigger, value) }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class Transporter
|
31
|
+
include EventGenerator
|
32
|
+
|
33
|
+
def initialize
|
34
|
+
@callbacks = {}
|
35
|
+
end
|
36
|
+
|
37
|
+
def connect(timeout=5)
|
38
|
+
signal(:connect)
|
39
|
+
true
|
40
|
+
end
|
41
|
+
|
42
|
+
def disconnect
|
43
|
+
signal(:disconnect)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class HTTPTransporter < Transporter
|
48
|
+
def initialize(uri)
|
49
|
+
super()
|
50
|
+
@uri = uri
|
51
|
+
@http = Net::HTTP.new(@uri.host, @uri.port)
|
52
|
+
@http.use_ssl = @uri.scheme == 'https'
|
53
|
+
@http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
54
|
+
end
|
55
|
+
|
56
|
+
def response_body(result)
|
57
|
+
return nil unless result.code.to_i >= 200 and result.code.to_i < 300
|
58
|
+
|
59
|
+
jsonrpc = symbolify JSON.parse(result.body)
|
60
|
+
return nil unless jsonrpc[:jsonrpc] == "2.0"
|
61
|
+
return nil unless jsonrpc[:error].nil?
|
62
|
+
return nil if jsonrpc[:result].nil?
|
63
|
+
|
64
|
+
return jsonrpc[:result][:body] unless jsonrpc[:result][:body].nil?
|
65
|
+
|
66
|
+
return jsonrpc[:result]
|
67
|
+
end
|
68
|
+
|
69
|
+
def send_message(msg)
|
70
|
+
req = Net::HTTP::Post.new(@uri.to_s,
|
71
|
+
initheader = { 'Content-Type' => 'application/json', 'Accept' => 'application/json' }
|
72
|
+
)
|
73
|
+
req.body = msg
|
74
|
+
signal(:sent, msg)
|
75
|
+
response = @http.request(req)
|
76
|
+
signal(:message, response.body)
|
77
|
+
|
78
|
+
response
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
class WebSocketTransporter < Transporter
|
83
|
+
attr_reader :ready
|
84
|
+
|
85
|
+
def initialize(uri)
|
86
|
+
super()
|
87
|
+
@uri = uri
|
88
|
+
@queue = []
|
89
|
+
@ready = false
|
90
|
+
@mutex = Mutex.new
|
91
|
+
@condition = ConditionVariable.new
|
92
|
+
end
|
93
|
+
|
94
|
+
def connect(timeout=5)
|
95
|
+
return true if @ready
|
96
|
+
start_socket
|
97
|
+
@mutex.synchronize { @condition.wait(@mutex, timeout) }
|
98
|
+
@ready
|
99
|
+
end
|
100
|
+
|
101
|
+
def disconnect
|
102
|
+
@client.kill unless @client.nil?
|
103
|
+
@client = nil
|
104
|
+
@ws = nil
|
105
|
+
true
|
106
|
+
end
|
107
|
+
|
108
|
+
def start_socket
|
109
|
+
@client = Thread.new do
|
110
|
+
EM.run do
|
111
|
+
@ws = Faye::WebSocket::Client.new(@uri.to_s)
|
112
|
+
|
113
|
+
@ws.on :open do |event|
|
114
|
+
@mutex.synchronize do
|
115
|
+
@ready = true
|
116
|
+
@condition.signal
|
117
|
+
end
|
118
|
+
|
119
|
+
signal(:connect)
|
120
|
+
empty_queue
|
121
|
+
end
|
122
|
+
|
123
|
+
@ws.on :error do |event|
|
124
|
+
@mutex.synchronize { @condition.signal }
|
125
|
+
end
|
126
|
+
|
127
|
+
@ws.on :message do |event|
|
128
|
+
signal(:message, event.data)
|
129
|
+
end
|
130
|
+
|
131
|
+
@ws.on :close do |event|
|
132
|
+
@ready = false
|
133
|
+
@ws = nil
|
134
|
+
signal(:disconnect)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def empty_queue
|
141
|
+
@queue.each { |msg| send_message(msg) }
|
142
|
+
@queue = []
|
143
|
+
end
|
144
|
+
|
145
|
+
def send_message(msg)
|
146
|
+
unless @ready then
|
147
|
+
@queue.push msg
|
148
|
+
return
|
149
|
+
end
|
150
|
+
|
151
|
+
signal(:sent, msg)
|
152
|
+
@ws.send(msg)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
class AcresClient
|
157
|
+
include EventGenerator
|
158
|
+
|
159
|
+
attr_reader :uri, :token, :server_info, :history, :start_time, :connect_time, :connected, :ready
|
160
|
+
attr_accessor :username, :password, :version
|
161
|
+
|
162
|
+
def initialize(destination, username=nil, password=nil)
|
163
|
+
if destination.is_a? String then
|
164
|
+
@uri = URI(destination)
|
165
|
+
else
|
166
|
+
@uri = URI(destination[:url] + destination[:ctx] + "/json?wrap")
|
167
|
+
end
|
168
|
+
|
169
|
+
@username = username
|
170
|
+
@password = password
|
171
|
+
@history = []
|
172
|
+
|
173
|
+
case @uri.scheme
|
174
|
+
when 'http', 'https'
|
175
|
+
@transport = HTTPTransporter.new(@uri)
|
176
|
+
when 'ws', 'wss'
|
177
|
+
@transport = WebSocketTransporter.new(@uri)
|
178
|
+
end
|
179
|
+
|
180
|
+
passthrough(@transport)
|
181
|
+
@transport.on(:message) { |trigger, message| snoop_token(message) }
|
182
|
+
@transport.on(:message) { |trigger, message| add_message_to_history(:server, message) }
|
183
|
+
@transport.on(:sent) { |trigger, message| add_message_to_history(:client, message) }
|
184
|
+
@transport.on(:connect) { |trigger, message| @ready = true }
|
185
|
+
@transport.on(:disconnect) do |trigger, message|
|
186
|
+
@ready = false
|
187
|
+
@start_time = nil
|
188
|
+
end
|
189
|
+
|
190
|
+
@connected = false
|
191
|
+
@connect_time = nil
|
192
|
+
@start_time = nil
|
193
|
+
@authenticated = false
|
194
|
+
@token = nil
|
195
|
+
end
|
196
|
+
|
197
|
+
def add_message_to_history(sender, message, timestamp=nil)
|
198
|
+
timestamp ||= Time.now
|
199
|
+
@start_time ||= timestamp
|
200
|
+
history.push({ time: timestamp, sender: sender, message: (symbolify(JSON.parse(message)) rescue nil), raw:message })
|
201
|
+
end
|
202
|
+
|
203
|
+
def snoop_token(message)
|
204
|
+
begin
|
205
|
+
response = symbolify JSON.parse(message)
|
206
|
+
result = response[:result]
|
207
|
+
return if attempt_snoop(result)
|
208
|
+
attempt_snoop(result[:body]) unless result.nil? or result[:body].nil?
|
209
|
+
rescue
|
210
|
+
""
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def attempt_snoop(result)
|
215
|
+
if not(result.nil?) and result.has_key? :tok and result.has_key? :serverInfo then
|
216
|
+
# this is a connection token
|
217
|
+
@connected = true
|
218
|
+
@token = result[:tok] if result.has_key? :tok
|
219
|
+
@server_info = result[:serverInfo] if result.has_key? :serverInfo
|
220
|
+
signal(:connect)
|
221
|
+
true
|
222
|
+
else
|
223
|
+
false
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def transport_connect
|
228
|
+
@transport.connect
|
229
|
+
end
|
230
|
+
|
231
|
+
def disconnect
|
232
|
+
@transport.disconnect
|
233
|
+
end
|
234
|
+
|
235
|
+
def connect
|
236
|
+
return nil unless transport_connect
|
237
|
+
|
238
|
+
request = {
|
239
|
+
appInfo: app_info,
|
240
|
+
deviceInfo: device_info
|
241
|
+
}
|
242
|
+
|
243
|
+
result = send_message(wrapped_message("connect", "com.acres4.common.info.ConnectionRequest", request))
|
244
|
+
response = response_body(result)
|
245
|
+
return nil unless response
|
246
|
+
|
247
|
+
response
|
248
|
+
end
|
249
|
+
|
250
|
+
def connect_if_needed
|
251
|
+
return true if @connected
|
252
|
+
return true unless connect.nil?
|
253
|
+
return false
|
254
|
+
end
|
255
|
+
|
256
|
+
def authenticate(username=nil, password=nil)
|
257
|
+
return nil unless connect_if_needed
|
258
|
+
|
259
|
+
request = {
|
260
|
+
tok: @token,
|
261
|
+
username: username || @username,
|
262
|
+
password: make_password(password || @password),
|
263
|
+
automaticReconnect: false
|
264
|
+
}
|
265
|
+
|
266
|
+
result = send_message(wrapped_message("login", "com.acres4.common.info.ClientLoginRequest", request))
|
267
|
+
response = response_body(result)
|
268
|
+
@authenticated = not(response.nil?)
|
269
|
+
response
|
270
|
+
end
|
271
|
+
|
272
|
+
def authenticate_if_needed(username=nil, password=nil)
|
273
|
+
return true if @authenticated
|
274
|
+
return true unless authenticate(username, password).nil?
|
275
|
+
return false
|
276
|
+
end
|
277
|
+
|
278
|
+
def make_password(password)
|
279
|
+
{
|
280
|
+
algorithm:"CLEARTEXT",
|
281
|
+
contents:password,
|
282
|
+
parameters:[]
|
283
|
+
}
|
284
|
+
end
|
285
|
+
|
286
|
+
def next_msg_id
|
287
|
+
@msg_id = @msg_id.nil? ? 0 : @msg_id + 1
|
288
|
+
end
|
289
|
+
|
290
|
+
def wrapped_message(method, cls, body)
|
291
|
+
cls ||= talk.guess_class(body)
|
292
|
+
body[:__class] = cls.to_s;
|
293
|
+
body[:tok] = @token unless @token.nil?
|
294
|
+
|
295
|
+
wrapper = {
|
296
|
+
className:cls,
|
297
|
+
body:body,
|
298
|
+
namedObjectEncodingType:0,
|
299
|
+
serializedEncoding:nil,
|
300
|
+
__class:"com.acres4.common.info.NamedObjectWrapper"
|
301
|
+
}
|
302
|
+
|
303
|
+
jsonrpc_message(method, nil, "com.acres4.common.info.NamedObjectWrapper", wrapper)
|
304
|
+
end
|
305
|
+
|
306
|
+
def inject_token(message, force=false, type=nil)
|
307
|
+
return nil if message.nil?
|
308
|
+
return message unless force or message[:tok].nil?
|
309
|
+
type ||= talk.guess_class(message)
|
310
|
+
unless type.nil? then
|
311
|
+
cls = talk.class_named(type)
|
312
|
+
has_tok = (cls[:field].select do |f|
|
313
|
+
is_token_class = talk.name_matches?("com.acres4.common.info.ConnectionToken", f[:type].first)
|
314
|
+
f[:name] == "tok" and is_token_class
|
315
|
+
end).length >= 1
|
316
|
+
|
317
|
+
message[:tok] = @token if has_tok
|
318
|
+
end
|
319
|
+
|
320
|
+
message
|
321
|
+
end
|
322
|
+
|
323
|
+
def jsonrpc_message(method, interface=nil, cls=nil, body={})
|
324
|
+
body[:__class] = (cls || talk.guess_class(body)) unless body.nil?
|
325
|
+
|
326
|
+
json = {
|
327
|
+
__class:"com.acres4.common.info.JSONRPCRequest",
|
328
|
+
method:method,
|
329
|
+
params:body,
|
330
|
+
id:next_msg_id,
|
331
|
+
jsonrpc:"2.0"
|
332
|
+
}
|
333
|
+
|
334
|
+
json[:intf] = interface unless interface.nil? # don't even include it if we didn't specify it
|
335
|
+
json
|
336
|
+
end
|
337
|
+
|
338
|
+
def empty_query(method, interface=nil)
|
339
|
+
jsonrpc_message(method, interface)
|
340
|
+
end
|
341
|
+
|
342
|
+
def send_message(message)
|
343
|
+
message = message.to_json unless message.is_a? String
|
344
|
+
@transport.send_message(message)
|
345
|
+
end
|
346
|
+
|
347
|
+
def response_body(result)
|
348
|
+
return nil unless result.code.to_i >= 200 and result.code.to_i < 300
|
349
|
+
|
350
|
+
jsonrpc = JSON.parse(result.body)
|
351
|
+
return nil unless jsonrpc["jsonrpc"] == "2.0"
|
352
|
+
return nil unless jsonrpc["error"].nil?
|
353
|
+
return nil if jsonrpc["result"].nil?
|
354
|
+
|
355
|
+
return symbolify jsonrpc["result"]["body"] unless jsonrpc["result"]["body"].nil?
|
356
|
+
|
357
|
+
return symbolify jsonrpc["result"]
|
358
|
+
end
|
359
|
+
|
360
|
+
def app_info
|
361
|
+
info = ObjectBuilder[:app_info_for_script].value
|
362
|
+
info[:currentTalkVersion] = @version unless @version.nil?
|
363
|
+
info
|
364
|
+
end
|
365
|
+
|
366
|
+
def device_info
|
367
|
+
ObjectBuilder[:device_info_for_system].value
|
368
|
+
end
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
require 'clients/caching_client.rb'
|
373
|
+
require 'clients/deployment_client.rb'
|
374
|
+
require 'clients/kai_config_client.rb'
|
375
|
+
require 'clients/usher_client.rb'
|
376
|
+
require 'clients/usher_mgmt_client.rb'
|
@@ -0,0 +1,151 @@
|
|
1
|
+
module A4Tools
|
2
|
+
class AuthenticationError < StandardError
|
3
|
+
end
|
4
|
+
|
5
|
+
class UpdateError < StandardError
|
6
|
+
end
|
7
|
+
|
8
|
+
class CachingClient < AcresClient
|
9
|
+
attr_accessor :timeout
|
10
|
+
|
11
|
+
class << self
|
12
|
+
attr_accessor :cache_methods, :query_methods
|
13
|
+
def cache(name, params={}, &blk)
|
14
|
+
@cache_methods ||= {}
|
15
|
+
@cache_methods[name] = {
|
16
|
+
params:params,
|
17
|
+
block:blk
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
def query(name, params={}, &blk)
|
22
|
+
@query_methods ||= {}
|
23
|
+
@query_methods[name] = {
|
24
|
+
params:params,
|
25
|
+
block:blk
|
26
|
+
}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(destination, username=nil, password=nil, timeout=300)
|
31
|
+
super(destination, username, password)
|
32
|
+
@timeout = timeout
|
33
|
+
@query_cache = {}
|
34
|
+
@cache = {}
|
35
|
+
@update_thread = nil
|
36
|
+
@query_methods = self.class.query_methods.clone rescue {}
|
37
|
+
end
|
38
|
+
|
39
|
+
def send_update_request
|
40
|
+
new_cache = {}
|
41
|
+
return {} if self.class.cache_methods.nil?
|
42
|
+
self.class.cache_methods.each do |method, defn|
|
43
|
+
if defn[:params][:authenticate] then
|
44
|
+
raise(AuthenticationError, "Unable to authenticate") unless authenticate_if_needed
|
45
|
+
end
|
46
|
+
|
47
|
+
request = defn[:block].nil? ? nil : defn[:block].call
|
48
|
+
new_cache[method] = update_from_method(method, request)
|
49
|
+
end
|
50
|
+
new_cache
|
51
|
+
end
|
52
|
+
|
53
|
+
def update_from_method(method, request, type=nil)
|
54
|
+
type ||= talk.expand_name(talk.method_named(method.to_s)[0][:request])
|
55
|
+
inject_token(request, false, type)
|
56
|
+
body = request.nil? ? empty_query(method) : wrapped_message(method.to_s, type, request)
|
57
|
+
result = send_message(body)
|
58
|
+
begin
|
59
|
+
parsed = symbolify(JSON.parse(result.body))
|
60
|
+
raise(UpdateError, "#{method}: #{parsed[:error][:code]} #{parsed[:error][:message]}") unless parsed[:error].nil?
|
61
|
+
raise(UpdateError, "#{method}: Server did not include a response field") if parsed[:result].nil?
|
62
|
+
toplevel = parsed[:result]
|
63
|
+
if toplevel.has_key?(:body) and toplevel.has_key?(:className) then
|
64
|
+
toplevel = toplevel[:body]
|
65
|
+
toplevel[:__class] = parsed[:result][:className] # strip namedobjectwrapper
|
66
|
+
end
|
67
|
+
toplevel
|
68
|
+
rescue JSON::ParserError
|
69
|
+
truncated = result.body.length > 100 ? result.body[0..99] + "..." : result.body
|
70
|
+
raise(UpdateError, "#{method}: Server returned non-JSON response: (#{result.body.length} bytes) '#{truncated}'")
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def [](key)
|
75
|
+
cache(key)
|
76
|
+
end
|
77
|
+
|
78
|
+
def cache(key, sync=false)
|
79
|
+
if sync then
|
80
|
+
refresh unless fresh? # force caller to wait until we've refreshed
|
81
|
+
else
|
82
|
+
refresh_async unless fresh? # kick off an async update if we're stale, but still return what we have
|
83
|
+
end
|
84
|
+
|
85
|
+
@cache[key]
|
86
|
+
end
|
87
|
+
|
88
|
+
def query(method, *args)
|
89
|
+
defn = @query_methods[method]
|
90
|
+
raise UpdateError, "#{method}: Unsupported method" if defn.nil?
|
91
|
+
|
92
|
+
if defn[:params][:authenticate] then
|
93
|
+
raise(AuthenticationError, "Unable to authenticate as #{username}") unless authenticate_if_needed
|
94
|
+
end
|
95
|
+
|
96
|
+
@query_cache[method] ||= {}
|
97
|
+
item = @query_cache[method] || {}
|
98
|
+
return item[:cache] unless timestamp_expired?(item[:update])
|
99
|
+
|
100
|
+
request = defn[:block].nil? ? nil : defn[:block].call(*args)
|
101
|
+
result = update_from_method(method, request)
|
102
|
+
item[:update] = Time.now
|
103
|
+
item[:cache] = result
|
104
|
+
@query_cache[method] = item
|
105
|
+
result
|
106
|
+
end
|
107
|
+
|
108
|
+
# Force a cache update next time someone makes a request
|
109
|
+
def dirty
|
110
|
+
@cache_update_ts = nil
|
111
|
+
@query_methods.each { |method, defn| defn[:update] = nil }
|
112
|
+
end
|
113
|
+
|
114
|
+
def ensure_fresh
|
115
|
+
refresh unless fresh?
|
116
|
+
end
|
117
|
+
|
118
|
+
def fresh?
|
119
|
+
not timestamp_expired?(@cache_update_ts)
|
120
|
+
end
|
121
|
+
|
122
|
+
def timestamp_expired?(ts)
|
123
|
+
ts.nil? or (Time.now - ts) > @timeout
|
124
|
+
end
|
125
|
+
|
126
|
+
def refresh_async
|
127
|
+
# Create a new thread to get the update cache, unless one is already in flight
|
128
|
+
if @update_thread.nil? then
|
129
|
+
begin
|
130
|
+
Thread.new { refresh }
|
131
|
+
rescue AuthenticationError, UpdateError => exc
|
132
|
+
signal(:error, exc.message)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
nil
|
136
|
+
end
|
137
|
+
|
138
|
+
def refresh
|
139
|
+
unless @update_thread.nil? then
|
140
|
+
# We already have an update thread in flight; block on that
|
141
|
+
@update_thread.join unless @update_thread == Thread.current
|
142
|
+
return @cache
|
143
|
+
end
|
144
|
+
|
145
|
+
@update_thread = Thread.current
|
146
|
+
@cache = send_update_request
|
147
|
+
@cache_update_ts = Time.now
|
148
|
+
@update_thread = nil
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|