spire_io 1.0.0.alpha.2 → 1.0.0.alpha.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
-
|