spire_io 1.0.0.alpha.2 → 1.0.0.alpha.5
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/spire_io.rb +149 -485
- metadata +4 -5
- data/lib/requestable.rb +0 -70
data/lib/spire_io.rb
CHANGED
@@ -1,205 +1,25 @@
|
|
1
|
+
require "delegate"
|
1
2
|
gem "excon"
|
2
3
|
require "excon"
|
3
4
|
gem "json"
|
4
5
|
require "json"
|
5
6
|
|
6
|
-
require "
|
7
|
+
require "spire/api"
|
7
8
|
|
8
9
|
class Spire
|
9
10
|
|
10
11
|
#How many times we will try to create a channel or subscription after getting a 409
|
11
12
|
RETRY_CREATION_LIMIT = 3
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
define_request(:discover) do
|
16
|
-
{
|
17
|
-
:method => :get,
|
18
|
-
:url => @url,
|
19
|
-
:headers => {"Accept" => "application/json"}
|
20
|
-
}
|
21
|
-
end
|
22
|
-
|
23
|
-
define_request(:start) do |key|
|
24
|
-
{
|
25
|
-
:method => :post,
|
26
|
-
:url => @description["resources"]["sessions"]["url"],
|
27
|
-
:body => {:key => key}.to_json,
|
28
|
-
:headers => {
|
29
|
-
"Accept" => mediaType("session"),
|
30
|
-
"Content-Type" => mediaType("account")
|
31
|
-
}
|
32
|
-
}
|
33
|
-
end
|
34
|
-
|
35
|
-
define_request(:login) do |email, password|
|
36
|
-
{
|
37
|
-
:method => :post,
|
38
|
-
:url => @description["resources"]["sessions"]["url"],
|
39
|
-
:body => { :email => email, :password => password }.to_json,
|
40
|
-
:headers => {
|
41
|
-
"Accept" => mediaType("session"),
|
42
|
-
"Content-Type" => mediaType("account")
|
43
|
-
}
|
44
|
-
}
|
45
|
-
end
|
46
|
-
|
47
|
-
define_request(:register) do |info|
|
48
|
-
{
|
49
|
-
:method => :post,
|
50
|
-
:url => @description["resources"]["accounts"]["url"],
|
51
|
-
:body => {
|
52
|
-
:email => info[:email],
|
53
|
-
:password => info[:password],
|
54
|
-
:password_confirmation => info[:password_confirmation]
|
55
|
-
}.to_json,
|
56
|
-
:headers => {
|
57
|
-
"Accept" => mediaType("session"),
|
58
|
-
"Content-Type" => mediaType("account")
|
59
|
-
}
|
60
|
-
}
|
61
|
-
end
|
62
|
-
|
63
|
-
define_request(:session) do
|
64
|
-
{
|
65
|
-
:method => :get,
|
66
|
-
:url => @session["url"],
|
67
|
-
:headers => {
|
68
|
-
"Accept" => mediaType("session"),
|
69
|
-
"Authorization" => "Capability #{@session["capability"]}"
|
70
|
-
}
|
71
|
-
}
|
72
|
-
end
|
73
|
-
|
74
|
-
define_request(:password_reset) do |email|
|
75
|
-
{
|
76
|
-
:method => :post,
|
77
|
-
:url => @description["resources"]["accounts"]["url"],
|
78
|
-
:body => ""
|
79
|
-
}
|
80
|
-
end
|
81
|
-
|
82
|
-
define_request(:delete_account) do
|
83
|
-
{
|
84
|
-
:method => :delete,
|
85
|
-
:url => @resources["account"]["url"],
|
86
|
-
:headers => {
|
87
|
-
"Accept" => mediaType("account"),"Content-Type" => mediaType("account"),
|
88
|
-
"Authorization" =>
|
89
|
-
"Capability #{@resources["account"]["capability"]}"
|
90
|
-
}
|
91
|
-
}
|
92
|
-
end
|
93
|
-
|
94
|
-
define_request(:update_account) do |info|
|
95
|
-
{
|
96
|
-
:method => :put,
|
97
|
-
:url => @resources["account"]["url"],
|
98
|
-
:body => info.to_json,
|
99
|
-
:headers => {
|
100
|
-
"Accept" => mediaType("account"),"Content-Type" => mediaType("account"),
|
101
|
-
"Authorization" => "Capability #{@resources["account"]["capability"]}"
|
102
|
-
}
|
103
|
-
}
|
104
|
-
end
|
105
|
-
|
106
|
-
define_request(:create_channel) do |name|
|
107
|
-
{
|
108
|
-
:method => :post,
|
109
|
-
:url => @resources["channels"]["url"],
|
110
|
-
:body => { :name => name }.to_json,
|
111
|
-
:headers => {
|
112
|
-
"Authorization" =>
|
113
|
-
"Capability #{@resources["channels"]["capability"]}",
|
114
|
-
"Accept" => mediaType("channel"),
|
115
|
-
"Content-Type" => mediaType("channel")
|
116
|
-
}
|
117
|
-
}
|
118
|
-
end
|
119
|
-
|
120
|
-
define_request(:channels) do
|
121
|
-
{
|
122
|
-
:method => :get,
|
123
|
-
:url => @resources["channels"]["url"],
|
124
|
-
:headers => {
|
125
|
-
"Authorization" =>
|
126
|
-
"Capability #{@resources["channels"]["capability"]}",
|
127
|
-
"Accept" => mediaType("channels"),
|
128
|
-
}
|
129
|
-
}
|
130
|
-
end
|
131
|
-
|
132
|
-
define_request(:subscribe) do |subscription_name, channels|
|
133
|
-
{
|
134
|
-
:method => :post,
|
135
|
-
:url => @resources["subscriptions"]["url"],
|
136
|
-
:body => {
|
137
|
-
:channels => channels.flatten.map { |name| self[name].url },
|
138
|
-
:name => subscription_name
|
139
|
-
}.to_json,
|
140
|
-
:headers => {
|
141
|
-
"Authorization" => "Capability #{@resources["subscriptions"]["capability"]}",
|
142
|
-
"Accept" => mediaType("subscription"),
|
143
|
-
"Content-Type" => mediaType("subscription")
|
144
|
-
}
|
145
|
-
}
|
146
|
-
end
|
147
|
-
|
148
|
-
define_request(:billing) do
|
149
|
-
{
|
150
|
-
:method => :get,
|
151
|
-
:url => @description["resources"]["billing"]["url"],
|
152
|
-
:headers => {
|
153
|
-
"Accept" => "application/json"
|
154
|
-
}
|
155
|
-
}
|
156
|
-
end
|
157
|
-
|
158
|
-
define_request(:billing_subscription) do |info|
|
159
|
-
{
|
160
|
-
:method => :put,
|
161
|
-
:url => @resources["account"]["billing"]["url"],
|
162
|
-
:body => info.to_json,
|
163
|
-
:headers => {
|
164
|
-
"Accept" => mediaType("account"),"Content-Type" => mediaType("account"),
|
165
|
-
"Authorization" => "Capability #{@resources["account"]["billing"]["capability"]}"
|
166
|
-
}
|
167
|
-
}
|
168
|
-
end
|
169
|
-
|
170
|
-
define_request(:billing_invoices) do
|
171
|
-
{
|
172
|
-
:method => :get,
|
173
|
-
:url => @resources["account"]["billing"]["invoices"]["url"],
|
174
|
-
:headers => {
|
175
|
-
"Accept" => "application/json",
|
176
|
-
"Authorization" => "Capability #{@resources["account"]["billing"]["invoices"]["capability"]}"
|
177
|
-
}
|
178
|
-
}
|
179
|
-
end
|
180
|
-
|
181
|
-
define_request(:billing_invoices_upcoming) do
|
182
|
-
{
|
183
|
-
:method => :get,
|
184
|
-
:url => @resources["account"]["billing"]["invoices"]["upcoming"]["url"],
|
185
|
-
:headers => {
|
186
|
-
"Accept" => "application/json",
|
187
|
-
"Authorization" => "Capability #{@resources["account"]["billing"]["invoices"]["upcoming"]["capability"]}"
|
188
|
-
}
|
189
|
-
}
|
190
|
-
end
|
191
|
-
|
192
|
-
attr_accessor :client, :channels, :session, :resources
|
14
|
+
attr_accessor :api, :session, :resources
|
193
15
|
|
194
16
|
def initialize(url="https://api.spire.io")
|
195
|
-
@
|
17
|
+
@api = Spire::API.new(url)
|
196
18
|
@url = url
|
197
19
|
@channels = {}
|
198
20
|
@subscriptions = {}
|
199
21
|
@channel_error_counts = {}
|
200
22
|
@subscription_error_counts = {}
|
201
|
-
# @headers = { "User-Agent" => "Ruby spire.io client" }
|
202
|
-
# @timeout = 1
|
203
23
|
discover
|
204
24
|
end
|
205
25
|
|
@@ -212,25 +32,18 @@ class Spire
|
|
212
32
|
end
|
213
33
|
|
214
34
|
def discover
|
215
|
-
|
216
|
-
raise "Error during discovery: #{response.status}" if response.status != 200
|
217
|
-
@description = JSON.parse(response.body)
|
218
|
-
#pp @description["schema"]["1.0"]
|
35
|
+
@api.discover
|
219
36
|
self
|
220
37
|
end
|
221
38
|
|
222
39
|
def start(key)
|
223
|
-
|
224
|
-
raise "Error starting a key-based session" if response.status != 201
|
225
|
-
cache_session(JSON.parse(response.body))
|
40
|
+
@session = @api.create_session(key)
|
226
41
|
self
|
227
42
|
end
|
228
43
|
|
229
44
|
# Authenticates a session using a login and password
|
230
45
|
def login(login, password)
|
231
|
-
|
232
|
-
raise "Error attemping to login: (#{response.status}) #{response.body}" if response.status != 201
|
233
|
-
cache_session(JSON.parse(response.body))
|
46
|
+
@session = @api.login(login, password)
|
234
47
|
self
|
235
48
|
end
|
236
49
|
|
@@ -239,12 +52,14 @@ class Spire
|
|
239
52
|
# @param [String] :password Password of new account
|
240
53
|
# @param [String] :password_confirmation Password confirmation (optional)
|
241
54
|
def register(info)
|
242
|
-
|
243
|
-
raise "Error attempting to register: (#{response.status}) #{response.body}" if response.status != 201
|
244
|
-
cache_session(JSON.parse(response.body))
|
55
|
+
@session = @api.create_account(info)
|
245
56
|
self
|
246
57
|
end
|
247
58
|
|
59
|
+
def key
|
60
|
+
@session.resources["account"]["key"]
|
61
|
+
end
|
62
|
+
|
248
63
|
def password_reset_request(email)
|
249
64
|
response = request(:password_reset)
|
250
65
|
unless response.status == 202
|
@@ -256,15 +71,16 @@ class Spire
|
|
256
71
|
|
257
72
|
# Deletes the currently authenticated account
|
258
73
|
def delete_account
|
259
|
-
|
74
|
+
@session.account.delete
|
260
75
|
end
|
261
76
|
|
262
77
|
# Updates the current account with the new account information
|
263
78
|
# See Spire docs for available settings
|
264
79
|
def update(info)
|
265
|
-
|
266
|
-
|
267
|
-
|
80
|
+
@session.account.update(info)
|
81
|
+
#response = request(:update_account, info)
|
82
|
+
#raise "Error attempting to update account: (#{response.status}) #{response.body}" if response.status != 200
|
83
|
+
#@resources["account"] = JSON.parse(response.body)
|
268
84
|
self
|
269
85
|
end
|
270
86
|
|
@@ -308,152 +124,120 @@ class Spire
|
|
308
124
|
# @param [String] name Name of channel returned
|
309
125
|
# @return [Channel]
|
310
126
|
def [](name)
|
311
|
-
|
312
|
-
|
127
|
+
Channel.new(self, channels[name] || find_or_create_channel(name))
|
128
|
+
end
|
129
|
+
|
130
|
+
def channels
|
131
|
+
@session.channels
|
313
132
|
end
|
314
133
|
|
315
134
|
# Creates a channel on spire. Returns a Channel object. Note that this will
|
316
135
|
# fail with a 409 if a channel with the same name exists.
|
317
|
-
def
|
136
|
+
def find_or_create_channel(name)
|
318
137
|
@channel_error_counts[name] ||= 0
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
138
|
+
|
139
|
+
begin
|
140
|
+
return @session.create_channel(name)
|
141
|
+
# TODO custom error class for Conflict, which we can
|
142
|
+
# then match here, instead of testing for error message
|
143
|
+
rescue => error
|
144
|
+
if error.message =~ /409/
|
145
|
+
|
146
|
+
# Dear retry, I love you. Affectionately, Matthew.
|
147
|
+
if channel = @session.channels![name]
|
148
|
+
return channel
|
149
|
+
else
|
150
|
+
@channel_error_counts[name] += 1
|
151
|
+
retry unless @channel_error_counts >= RETRY_CREATION_LIMIT
|
152
|
+
end
|
153
|
+
|
154
|
+
else
|
155
|
+
raise error
|
156
|
+
end
|
323
157
|
end
|
324
|
-
new_channel = Channel.new(self,JSON.parse(response.body))
|
325
|
-
@channels[name] = new_channel
|
326
|
-
new_channel
|
327
158
|
end
|
328
159
|
|
329
|
-
def find_existing_channel(name)
|
330
|
-
@channel_error_counts[name] += 1
|
331
|
-
retrieve_session
|
332
|
-
self[name]
|
333
|
-
end
|
334
|
-
|
335
160
|
# Returns a subscription object for the given channels
|
336
161
|
# @param [String] subscription_name Name for the subscription
|
337
162
|
# @param [String] channels One or more channel names for the subscription to listen on
|
338
163
|
# @return [Subscription]
|
339
|
-
def subscribe(
|
164
|
+
def subscribe(name, *channels)
|
165
|
+
channels.each { |channel| self.find_or_create_channel(channel) }
|
166
|
+
Subscription.new(
|
167
|
+
@session.subscriptions[name] || find_or_create_subscription(name, *channels)
|
168
|
+
)
|
169
|
+
end
|
170
|
+
|
171
|
+
def find_or_create_subscription(subscription_name, *channels)
|
340
172
|
@subscription_error_counts[subscription_name] ||= 0
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
173
|
+
begin
|
174
|
+
return @session.create_subscription(subscription_name, channels)
|
175
|
+
rescue => error
|
176
|
+
if error.message =~ /409/
|
177
|
+
|
178
|
+
if subscription = @session.subscriptions![subscription_name]
|
179
|
+
return subscription
|
180
|
+
else
|
181
|
+
retry unless @subscription_error_counts >= RETRY_CREATION_LIMIT
|
182
|
+
end
|
183
|
+
|
184
|
+
else
|
185
|
+
raise error
|
186
|
+
end
|
187
|
+
end
|
349
188
|
end
|
189
|
+
|
350
190
|
alias :subscription :subscribe #For compatibility with other clients
|
351
191
|
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
192
|
+
#Returns an array of subscription objects for all of this account's subscriptions
|
193
|
+
#@return [Array]
|
194
|
+
def subscriptions
|
195
|
+
@session.subscriptions.values
|
356
196
|
end
|
357
197
|
|
358
198
|
# Returns a billing object than contains a list of all the plans available
|
359
199
|
# @param [String] info optional object description
|
360
200
|
# @return [Billing]
|
361
|
-
def billing
|
362
|
-
|
363
|
-
raise "Error getting billing plans: #{response.status}" if response.status != 200
|
364
|
-
Billing.new(self,JSON.parse(response.body))
|
201
|
+
def billing
|
202
|
+
@api.billing
|
365
203
|
end
|
366
204
|
|
367
205
|
# Updates and subscribe the account to a billing plan
|
368
206
|
# @param [Object] info data containing billing description
|
369
207
|
# @return [Account]
|
370
208
|
def billing_subscription(info)
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
209
|
+
@session.account.billing_subscription(info)
|
210
|
+
#response = request(:billing_subscription)
|
211
|
+
#raise "Error attempting to update account billing: (#{response.status}) #{response.body}" if response.status != 200
|
212
|
+
#@resources["account"] = JSON.parse(response.body)
|
213
|
+
#self
|
375
214
|
end
|
376
215
|
|
377
|
-
|
216
|
+
|
378
217
|
# Object representing a Spire channel
|
379
218
|
#
|
380
219
|
# You can get a channel object by calling [] on a Spire object
|
381
220
|
# * spire = Spire.new
|
382
221
|
# * spire.start("your api key")
|
383
222
|
# * channel = spire["channel name"]
|
384
|
-
class Channel
|
385
|
-
|
386
|
-
|
387
|
-
define_request(:publish) do |body|
|
388
|
-
{
|
389
|
-
:method => :post,
|
390
|
-
:url => url,
|
391
|
-
:body => body,
|
392
|
-
:headers => {
|
393
|
-
"Authorization" => "Capability #{@properties["capability"]}",
|
394
|
-
"Accept" => mediaType("message"),
|
395
|
-
"Content-Type" => mediaType("message")
|
396
|
-
}
|
397
|
-
}
|
398
|
-
end
|
399
|
-
|
400
|
-
define_request(:delete) do
|
401
|
-
{
|
402
|
-
:method => :delete,
|
403
|
-
:url => url,
|
404
|
-
:headers => {
|
405
|
-
"Authorization" => "Capability #{capability}"
|
406
|
-
}
|
407
|
-
}
|
408
|
-
end
|
409
|
-
|
410
|
-
def initialize(spire, properties)
|
223
|
+
class Channel < SimpleDelegator
|
224
|
+
def initialize(spire, channel)
|
225
|
+
super(channel)
|
411
226
|
@spire = spire
|
412
|
-
@client = spire.client
|
413
|
-
@properties = properties
|
414
|
-
end
|
415
|
-
|
416
|
-
def url
|
417
|
-
@properties["url"]
|
418
|
-
end
|
419
|
-
|
420
|
-
def key
|
421
|
-
@properties["key"]
|
422
|
-
end
|
423
|
-
|
424
|
-
def name
|
425
|
-
@properties["name"]
|
426
|
-
end
|
427
|
-
|
428
|
-
def capability
|
429
|
-
@properties["capability"]
|
430
|
-
end
|
431
|
-
|
432
|
-
def delete
|
433
|
-
response = request(:delete)
|
434
|
-
raise "Error deleting a channel" if response.status != 204
|
435
227
|
end
|
436
|
-
|
437
228
|
# Obtain a subscription for the channel
|
438
229
|
# @param [String] subscription_name Name of the subscription
|
439
230
|
# @return [Subscription]
|
440
231
|
def subscribe(subscription_name = nil)
|
441
|
-
@spire.subscribe(subscription_name,
|
442
|
-
end
|
443
|
-
|
444
|
-
#Publishes a message to the channel
|
445
|
-
# @param [String] message Message to be posted
|
446
|
-
# @return [Hash] response from the server
|
447
|
-
def publish(message)
|
448
|
-
response = request(:publish, {:content => message}.to_json)
|
449
|
-
raise "Error publishing a message: (#{response.status}) #{response.body}" if response.status != 201
|
450
|
-
JSON.parse(response.body)
|
232
|
+
@spire.subscribe(subscription_name, properties["name"])
|
451
233
|
end
|
452
234
|
|
453
|
-
|
454
|
-
|
235
|
+
# this is required because Delegator's method_missing relies
|
236
|
+
# on the object having a method defined, but in this case
|
237
|
+
# the API::Channel is also using method_missing
|
238
|
+
def name
|
239
|
+
__getobj__.name
|
455
240
|
end
|
456
|
-
|
457
241
|
end
|
458
242
|
|
459
243
|
# The subscription class represents a read connection to a Spire channel
|
@@ -468,214 +252,94 @@ class Spire
|
|
468
252
|
# *OR*
|
469
253
|
# * channel = spire["channel name"]
|
470
254
|
# * subscription = channel.subscribe("subscription name")
|
471
|
-
class Subscription
|
472
|
-
include Requestable
|
473
|
-
|
474
|
-
define_request(:listen) do |options|
|
475
|
-
timeout = options[:timeout]||30
|
476
|
-
delay = options[:delay]||0
|
477
|
-
order_by = options[:order_by]||'desc'
|
478
|
-
{
|
479
|
-
:method => :get,
|
480
|
-
:url => @properties["url"],
|
481
|
-
:query => {
|
482
|
-
"timeout" => timeout,
|
483
|
-
"last-message" => @last||'0',
|
484
|
-
"order-by" => order_by,
|
485
|
-
"delay" => delay
|
486
|
-
},
|
487
|
-
:headers => {
|
488
|
-
"Authorization" => "Capability #{@properties["capability"]}",
|
489
|
-
"Accept" => mediaType("events")
|
490
|
-
}
|
491
|
-
}
|
492
|
-
end
|
493
|
-
|
494
|
-
define_request(:delete) do
|
495
|
-
{
|
496
|
-
:method => :delete,
|
497
|
-
:url => url,
|
498
|
-
:headers => {
|
499
|
-
"Authorization" => "Capability #{capability}"
|
500
|
-
}
|
501
|
-
}
|
502
|
-
end
|
503
|
-
|
504
|
-
attr_accessor :messages, :last
|
505
|
-
|
506
|
-
def initialize(spire,properties)
|
507
|
-
@spire = spire
|
508
|
-
@client = spire.client
|
509
|
-
@properties = properties
|
510
|
-
@messages = []
|
511
|
-
@listening_thread = nil
|
512
|
-
@listeners = {}
|
513
|
-
@listening_threads = {}
|
514
|
-
@listener_mutex = Mutex.new
|
515
|
-
@listener_thread_mutex = Mutex.new
|
516
|
-
end
|
517
|
-
|
518
|
-
def key
|
519
|
-
@properties["key"]
|
520
|
-
end
|
255
|
+
class Subscription < SimpleDelegator
|
521
256
|
|
257
|
+
# this is required because Delegator's method_missing relies
|
258
|
+
# on the object having a method defined, but in this case
|
259
|
+
# the API::Subscription is also using method_missing
|
522
260
|
def name
|
523
|
-
|
524
|
-
end
|
525
|
-
|
526
|
-
def capability
|
527
|
-
@properties["capability"]
|
261
|
+
__getobj__.name
|
528
262
|
end
|
529
263
|
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
def delete
|
535
|
-
response = request(:delete)
|
536
|
-
raise "Error deleting a subscription" if response.status != 204
|
264
|
+
# misnamed method, here for backompat. Should be something like #get_messages,
|
265
|
+
# because it only makes one request.
|
266
|
+
def listen(options={})
|
267
|
+
long_poll(options).map {|message| message["content"] }
|
537
268
|
end
|
538
269
|
|
539
|
-
#
|
540
|
-
#
|
541
|
-
#
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
new_name = "Listener-#{rand(9999999)}"
|
549
|
-
listener_name = new_name unless @listeners.has_key?(new_name)
|
550
|
-
end
|
551
|
-
@listeners[listener_name] = block
|
552
|
-
end
|
553
|
-
listener_name
|
270
|
+
# wraps the underlying Subscription#add_listener to
|
271
|
+
# provided named listeners, threading, and a
|
272
|
+
# stop_listening method.
|
273
|
+
def add_listener(name=nil, &block)
|
274
|
+
raise ArgumentError unless block_given?
|
275
|
+
name ||= generate_listener_name
|
276
|
+
listener = wrap_listener(&block)
|
277
|
+
listeners[name] = listener
|
278
|
+
__getobj__.add_listener(&listener)
|
554
279
|
end
|
555
280
|
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
# @param [Boolean] kill_current_threads Kill any currently running threads of the removed listener
|
560
|
-
# @return [Proc] Listener that was removed
|
561
|
-
def remove_listener(name, kill_current_threads = true)
|
562
|
-
l = nil #scope
|
563
|
-
@listener_mutex.synchronize do
|
564
|
-
l = @listeners.delete(name)
|
281
|
+
def remove_listener(listener)
|
282
|
+
if listener.is_a? String
|
283
|
+
listener = listeners[listener]
|
565
284
|
end
|
566
|
-
|
567
|
-
l
|
285
|
+
__getobj__.listeners.delete(listener)
|
568
286
|
end
|
569
287
|
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
288
|
+
def wrap_listener(&block)
|
289
|
+
lambda do |message|
|
290
|
+
Thread.new do
|
291
|
+
# Messages received after a call to stop_listening
|
292
|
+
# will not be processed.
|
293
|
+
yield message["content"] if @listening
|
294
|
+
end
|
575
295
|
end
|
576
|
-
kill_listening_threads if kill_current_threads
|
577
|
-
true
|
578
296
|
end
|
579
297
|
|
580
|
-
|
581
|
-
|
582
|
-
# You can continue to add more listeners after starting the listening process
|
583
|
-
# @note Will raise an exception if listening has already been started
|
584
|
-
def start_listening
|
585
|
-
raise "Already listening" if @listening_thread
|
586
|
-
@listening_thread = Thread.new {
|
587
|
-
while true
|
588
|
-
new_messages = self.listen
|
589
|
-
next unless new_messages.size > 0
|
590
|
-
current_listeners.each do |name, listener|
|
591
|
-
new_messages.each do |m|
|
592
|
-
thread = Thread.new {
|
593
|
-
begin
|
594
|
-
listener.call(m)
|
595
|
-
rescue
|
596
|
-
puts "Error while running listener #{name}: #{$!.inspect}"
|
597
|
-
puts $!.backtrace.join("\n")
|
598
|
-
end
|
599
|
-
}
|
600
|
-
@listener_thread_mutex.synchronize do
|
601
|
-
@listening_threads[name] ||= []
|
602
|
-
@listening_threads[name] << thread
|
603
|
-
end
|
604
|
-
end
|
605
|
-
end
|
606
|
-
end
|
607
|
-
}
|
298
|
+
def listeners
|
299
|
+
@listeners ||= {}
|
608
300
|
end
|
609
301
|
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
@listening_thread = nil
|
616
|
-
end
|
617
|
-
kill_listening_threads if kill_current_threads
|
618
|
-
end
|
619
|
-
|
620
|
-
# Kills any currently executing listeners
|
621
|
-
# @param [String] name_to_kill Kill only currently executing listeners that have this name
|
622
|
-
def kill_listening_threads(name_to_kill = nil)
|
623
|
-
@listener_thread_mutex.synchronize do
|
624
|
-
@listening_threads.each do |name, threads|
|
625
|
-
next if name_to_kill and name_to_kill != name
|
626
|
-
threads.each {|t| t.kill }
|
627
|
-
@listening_threads[name] = []
|
628
|
-
end
|
302
|
+
def generate_listener_name
|
303
|
+
listener_name = nil
|
304
|
+
while !listener_name
|
305
|
+
new_name = "Listener-#{rand(9999999)}"
|
306
|
+
listener_name = new_name unless listeners.has_key?(new_name)
|
629
307
|
end
|
308
|
+
listener_name
|
630
309
|
end
|
631
310
|
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
# @return [Array] An array of messages received
|
637
|
-
def listen(options={})
|
638
|
-
response = request(:listen, options)
|
639
|
-
raise "Error listening for messages: (#{response.status}) #{response.body}" if response.status != 200
|
640
|
-
new_messages = JSON.parse(response.body)["messages"]
|
641
|
-
@listener_mutex.synchronize do
|
642
|
-
@last = new_messages.last["timestamp"] unless new_messages.empty?
|
643
|
-
new_messages.map! { |m| m["content"] }
|
644
|
-
@messages += new_messages
|
311
|
+
def start_listening(options={})
|
312
|
+
@listening = true
|
313
|
+
Thread.new do
|
314
|
+
long_poll(options) while @listening
|
645
315
|
end
|
646
|
-
new_messages
|
647
316
|
end
|
648
317
|
|
649
|
-
def
|
650
|
-
@
|
318
|
+
def stop_listening
|
319
|
+
@listening = false
|
651
320
|
end
|
652
321
|
|
653
|
-
private
|
654
|
-
def current_listeners
|
655
|
-
@listener_mutex.synchronize do #To prevent synch problems adding a new listener while looping
|
656
|
-
@listeners.dup
|
657
|
-
end
|
658
|
-
end
|
659
322
|
end
|
660
323
|
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
324
|
+
|
325
|
+
## Object representing a Spire billing
|
326
|
+
##
|
327
|
+
## You can get all the billing plans by calling the method billing in Spire object
|
328
|
+
## * spire = Spire.new
|
329
|
+
## * billing = spire.billing()
|
330
|
+
## * plans = billing.plans
|
331
|
+
#class Billing
|
332
|
+
#def initialize(spire,properties)
|
333
|
+
#@spire = spire
|
334
|
+
#@properties = properties
|
335
|
+
#end
|
672
336
|
|
673
|
-
def url
|
674
|
-
|
675
|
-
end
|
337
|
+
#def url
|
338
|
+
#@properties["url"]
|
339
|
+
#end
|
676
340
|
|
677
|
-
def plans
|
678
|
-
|
679
|
-
end
|
680
|
-
end
|
341
|
+
#def plans
|
342
|
+
#@properties["plans"]
|
343
|
+
#end
|
344
|
+
#end
|
681
345
|
end
|
metadata
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: spire_io
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash: -
|
4
|
+
hash: -3702664330
|
5
5
|
prerelease: true
|
6
6
|
segments:
|
7
7
|
- 1
|
8
8
|
- 0
|
9
9
|
- 0
|
10
10
|
- alpha
|
11
|
-
-
|
12
|
-
version: 1.0.0.alpha.
|
11
|
+
- 5
|
12
|
+
version: 1.0.0.alpha.5
|
13
13
|
platform: ruby
|
14
14
|
authors:
|
15
15
|
- Dan Yoder
|
@@ -18,7 +18,7 @@ autorequire:
|
|
18
18
|
bindir: bin
|
19
19
|
cert_chain: []
|
20
20
|
|
21
|
-
date: 2012-01-
|
21
|
+
date: 2012-01-27 00:00:00 -08:00
|
22
22
|
default_executable:
|
23
23
|
dependencies:
|
24
24
|
- !ruby/object:Gem::Dependency
|
@@ -93,7 +93,6 @@ extensions: []
|
|
93
93
|
extra_rdoc_files: []
|
94
94
|
|
95
95
|
files:
|
96
|
-
- lib/requestable.rb
|
97
96
|
- lib/spire_io.rb
|
98
97
|
has_rdoc: true
|
99
98
|
homepage: https://github.com/spire-io/spire.io.rb
|
data/lib/requestable.rb
DELETED
@@ -1,70 +0,0 @@
|
|
1
|
-
module Requestable
|
2
|
-
|
3
|
-
def self.included(mod)
|
4
|
-
mod.module_eval do
|
5
|
-
extend(ClassMethods)
|
6
|
-
include(InstanceMethods)
|
7
|
-
end
|
8
|
-
end
|
9
|
-
|
10
|
-
module ClassMethods
|
11
|
-
def requests
|
12
|
-
@requests ||= {}
|
13
|
-
end
|
14
|
-
|
15
|
-
def define_request(name, &block)
|
16
|
-
requests[name] = block
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
module InstanceMethods
|
21
|
-
def prepare_request(name, *args)
|
22
|
-
if block = self.class.requests[name]
|
23
|
-
options = self.instance_exec(*args, &block)
|
24
|
-
Request.new(@client, options)
|
25
|
-
else
|
26
|
-
raise ArgumentError, "No request has been defined for #{name.inspect}"
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def request(name, *args)
|
31
|
-
prepare_request(name, *args).exec
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
class Request
|
36
|
-
attr_accessor :url
|
37
|
-
def initialize(client, options)
|
38
|
-
@client = client
|
39
|
-
@method = options.delete(:method)
|
40
|
-
@url = options.delete(:url)
|
41
|
-
@options = options
|
42
|
-
@options[:headers] = {
|
43
|
-
"User-Agent" => "Ruby spire.io client"
|
44
|
-
}.merge(@options[:headers])
|
45
|
-
end
|
46
|
-
|
47
|
-
def headers
|
48
|
-
@options[:headers]
|
49
|
-
end
|
50
|
-
|
51
|
-
def body
|
52
|
-
@options[:body]
|
53
|
-
end
|
54
|
-
|
55
|
-
def body=(val)
|
56
|
-
@options[:body] = val
|
57
|
-
end
|
58
|
-
|
59
|
-
def query
|
60
|
-
@options[:query]
|
61
|
-
end
|
62
|
-
|
63
|
-
def exec
|
64
|
-
@client.send(@method, @url, @options)
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
end
|
69
|
-
|
70
|
-
|