spire_io 1.2.1 → 1.2.2

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.
@@ -25,10 +25,11 @@ class Spire
25
25
  end
26
26
 
27
27
  attr_reader :client, :description, :schema
28
- def initialize(url="https://api.spire.io", options={})
28
+ def initialize(url="https://api.spire.io", spire=nil, options={})
29
29
  @version = options[:version] || "1.0"
30
30
  @client = Excon
31
31
  @url = url
32
+ @spire = spire
32
33
  end
33
34
 
34
35
  define_request(:discover) do
@@ -166,22 +167,5 @@ class Spire
166
167
  end
167
168
  API::Application.new(self, response.data)
168
169
  end
169
-
170
- # Returns a billing object than contains a list of all the plans available
171
- # @param [String] info optional object description
172
- # @return [Billing]
173
- def billing(info=nil)
174
- response = request(:billing)
175
- raise "Error getting billing plans: #{response.status}" if response.status != 200
176
- API::Billing.new(self, response.data)
177
- end
178
-
179
- class Billing < Resource
180
- def resource_name
181
- "billing"
182
- end
183
- end
184
-
185
170
  end
186
-
187
171
  end
@@ -9,7 +9,7 @@ class Spire
9
9
  "application"
10
10
  end
11
11
 
12
- def initialize(spire, data)
12
+ def initialize(api, data)
13
13
  super
14
14
  @resources = data["resources"]
15
15
  end
@@ -31,8 +31,8 @@ class Spire
31
31
  :body => body,
32
32
  :headers => {
33
33
  "Authorization" => "Capability #{capability}",
34
- "Accept" => @spire.mediaType("channel"),
35
- "Content-Type" => @spire.mediaType("channel")
34
+ "Accept" => @api.mediaType("channel"),
35
+ "Content-Type" => @api.mediaType("channel")
36
36
  }
37
37
  }
38
38
  end
@@ -46,7 +46,7 @@ class Spire
46
46
  :url => url,
47
47
  :headers => {
48
48
  "Authorization" => "Capability #{capability}",
49
- "Accept" => @spire.mediaType("channels"),
49
+ "Accept" => @api.mediaType("channels"),
50
50
  }
51
51
  }
52
52
  end
@@ -61,7 +61,7 @@ class Spire
61
61
  :query => {:name => name},
62
62
  :headers => {
63
63
  "Authorization" => "Capability #{capability}",
64
- "Accept" => @spire.mediaType("channels"),
64
+ "Accept" => @api.mediaType("channels"),
65
65
  }
66
66
  }
67
67
  end
@@ -76,7 +76,7 @@ class Spire
76
76
  :url => url,
77
77
  :headers => {
78
78
  "Authorization" => "Capability #{capability}",
79
- "Accept" => @spire.mediaType("subscriptions"),
79
+ "Accept" => @api.mediaType("subscriptions"),
80
80
  }
81
81
  }
82
82
  end
@@ -94,8 +94,8 @@ class Spire
94
94
  }.to_json,
95
95
  :headers => {
96
96
  "Authorization" => "Capability #{capability}",
97
- "Accept" => @spire.mediaType("subscription"),
98
- "Content-Type" => @spire.mediaType("subscription")
97
+ "Accept" => @api.mediaType("subscription"),
98
+ "Content-Type" => @api.mediaType("subscription")
99
99
  }
100
100
  }
101
101
  end
@@ -110,7 +110,7 @@ class Spire
110
110
  :query => {:name => name},
111
111
  :headers => {
112
112
  "Authorization" => "Capability #{capability}",
113
- "Accept" => @spire.mediaType("subscriptions"),
113
+ "Accept" => @api.mediaType("subscriptions"),
114
114
  }
115
115
  }
116
116
  end
@@ -126,8 +126,8 @@ class Spire
126
126
  :body => data.to_json,
127
127
  :headers => {
128
128
  "Authorization" => "Capability #{capability}",
129
- "Accept" => @spire.mediaType("member"),
130
- "Content-Type" => @spire.mediaType("member")
129
+ "Accept" => @api.mediaType("member"),
130
+ "Content-Type" => @api.mediaType("member")
131
131
  }
132
132
  }
133
133
  end
@@ -141,7 +141,7 @@ class Spire
141
141
  :url => url,
142
142
  :headers => {
143
143
  "Authorization" => "Capability #{capability}",
144
- "Accept" => @spire.mediaType("members"),
144
+ "Accept" => @api.mediaType("members"),
145
145
  }
146
146
  }
147
147
  end
@@ -156,7 +156,7 @@ class Spire
156
156
  :query => {:login => login},
157
157
  :headers => {
158
158
  "Authorization" => "Capability #{capability}",
159
- "Accept" => @spire.mediaType("member"),
159
+ "Accept" => @api.mediaType("member"),
160
160
  }
161
161
  }
162
162
  end
@@ -169,7 +169,7 @@ class Spire
169
169
  :url => url,
170
170
  :body => data.to_json,
171
171
  :headers => {
172
- "Accept" => @spire.mediaType("member"),
172
+ "Accept" => @api.mediaType("member"),
173
173
  }
174
174
  }
175
175
  end
@@ -182,7 +182,7 @@ class Spire
182
182
  :method => :get,
183
183
  :url => url,
184
184
  :headers => {
185
- "Accept" => @spire.mediaType("member"),
185
+ "Accept" => @api.mediaType("member"),
186
186
  "Authorization" => "Basic #{auth}"
187
187
  }
188
188
  }
@@ -204,7 +204,7 @@ class Spire
204
204
  unless response.status == 200
205
205
  raise "Error authenticating for application #{self.name}: (#{response.status}) #{response.body}"
206
206
  end
207
- API::Member.new(@spire, response.data)
207
+ API::Member.new(@api, response.data)
208
208
  end
209
209
 
210
210
  #Alternative application authentication, without using basic auth
@@ -213,7 +213,7 @@ class Spire
213
213
  unless response.status == 201
214
214
  raise "Error authenticating for application #{self.name}: (#{response.status}) #{response.body}"
215
215
  end
216
- API::Member.new(@spire, response.data)
216
+ API::Member.new(@api, response.data)
217
217
  end
218
218
 
219
219
  #If you do not give a new password, you will get back an authenticated member but have to change
@@ -225,7 +225,7 @@ class Spire
225
225
  unless response.status == 201
226
226
  raise "Error reseting password for application #{self.name}: (#{response.status}) #{response.body}"
227
227
  end
228
- API::Member.new(@spire, response.data)
228
+ API::Member.new(@api, response.data)
229
229
  end
230
230
 
231
231
  def create_member(member_data)
@@ -233,7 +233,7 @@ class Spire
233
233
  unless response.status == 201
234
234
  raise "Error creating member for application #{self.name}: (#{response.status}) #{response.body}"
235
235
  end
236
- API::Member.new(@spire, response.data)
236
+ API::Member.new(@api, response.data)
237
237
  end
238
238
 
239
239
  #Resets a members password based on email
@@ -256,7 +256,7 @@ class Spire
256
256
  end
257
257
  @members = {}
258
258
  response.data.each do |login, properties|
259
- @members[login] = API::Member.new(@spire, properties)
259
+ @members[login] = API::Member.new(@api, properties)
260
260
  end
261
261
  @members
262
262
  end
@@ -267,7 +267,7 @@ class Spire
267
267
  raise "Error finding member with login #{member_login}: (#{response.status}) #{response.body}"
268
268
  end
269
269
  properties = response.data[member_login]
270
- member = API::Member.new(@spire, properties)
270
+ member = API::Member.new(@api, properties)
271
271
  @members[member_login] = member if @members.is_a?(Hash)
272
272
  member
273
273
  end
@@ -280,7 +280,7 @@ class Spire
280
280
  raise "Error creating Channel: (#{response.status}) #{response.body}"
281
281
  end
282
282
  properties = response.data
283
- channels[name] = API::Channel.new(@spire, properties)
283
+ channels[name] = API::Channel.new(@api, properties)
284
284
  end
285
285
 
286
286
  def create_subscription(subscription_name, channel_names)
@@ -290,7 +290,7 @@ class Spire
290
290
  raise "Error creating Subscription: (#{response.status}) #{response.body}"
291
291
  end
292
292
  data = response.data
293
- subscription = API::Subscription.new(@spire, data)
293
+ subscription = API::Subscription.new(@api, data)
294
294
  if subscription_name
295
295
  subscriptions[data["name"]] = subscription
296
296
  end
@@ -305,7 +305,7 @@ class Spire
305
305
  channels_data = response.data
306
306
  @channels = {}
307
307
  channels_data.each do |name, properties|
308
- @channels[name] = API::Channel.new(@spire, properties)
308
+ @channels[name] = API::Channel.new(@api, properties)
309
309
  end
310
310
  @channels
311
311
  end
@@ -325,7 +325,7 @@ class Spire
325
325
  end
326
326
  @subscriptions = {}
327
327
  response.data.each do |name, properties|
328
- @subscriptions[name] = API::Subscription.new(@spire, properties)
328
+ @subscriptions[name] = API::Subscription.new(@api, properties)
329
329
  end
330
330
  @subscriptions
331
331
  end
@@ -336,7 +336,7 @@ class Spire
336
336
  raise "Error finding channel with name #{name}: (#{response.status}) #{response.body}"
337
337
  end
338
338
  properties = response.data[name]
339
- channel = API::Channel.new(@spire, properties)
339
+ channel = API::Channel.new(@api, properties)
340
340
  @channels[name] = channel if @channels.is_a?(Hash)
341
341
  channel
342
342
  end
@@ -347,7 +347,7 @@ class Spire
347
347
  raise "Error finding subscription with name #{name}: (#{response.status}) #{response.body}"
348
348
  end
349
349
  properties = response.data[name]
350
- sub = API::Subscription.new(@spire, properties)
350
+ sub = API::Subscription.new(@api, properties)
351
351
  @subscriptions[name] = sub if @subscriptions.is_a?(Hash)
352
352
  sub
353
353
  end
@@ -1,6 +1,12 @@
1
1
  class Spire
2
2
  class API
3
3
 
4
+ # Object representing a Spire channel
5
+ #
6
+ # You can get a channel object by calling [] on a Spire object
7
+ # * spire = Spire.new
8
+ # * spire.start("your api secret")
9
+ # * channel = spire["channel name"]
4
10
  class Channel < Resource
5
11
 
6
12
  attr_reader :resources
@@ -23,7 +29,7 @@ class Spire
23
29
  :url => url,
24
30
  :headers => {
25
31
  "Authorization" => "Capability #{capability}",
26
- "Accept" => @spire.mediaType("subscriptions"),
32
+ "Accept" => @api.mediaType("subscriptions"),
27
33
  }
28
34
  }
29
35
  end
@@ -35,8 +41,8 @@ class Spire
35
41
  :body => string,
36
42
  :headers => {
37
43
  "Authorization" => "Capability #{@capabilities["publish"]}",
38
- "Accept" => @spire.mediaType("message"),
39
- "Content-Type" => @spire.mediaType("message")
44
+ "Accept" => @api.mediaType("message"),
45
+ "Content-Type" => @api.mediaType("message")
40
46
  }
41
47
  }
42
48
  end
@@ -51,8 +57,8 @@ class Spire
51
57
  :body => {:name => name}.to_json,
52
58
  :headers => {
53
59
  "Authorization" => "Capability #{capability}",
54
- "Accept" => @spire.mediaType("subscription"),
55
- "Content-Type" => @spire.mediaType("subscription")
60
+ "Accept" => @api.mediaType("subscription"),
61
+ "Content-Type" => @api.mediaType("subscription")
56
62
  }
57
63
  }
58
64
  end
@@ -68,7 +74,7 @@ class Spire
68
74
  end
69
75
  @subscriptions = {}
70
76
  response.data.each do |name, properties|
71
- @subscriptions[name] = API::Subscription.new(@spire, properties)
77
+ @subscriptions[name] = API::Subscription.new(@api, properties)
72
78
  end
73
79
  @subscriptions
74
80
  end
@@ -79,7 +85,7 @@ class Spire
79
85
  unless response.status == 201
80
86
  raise "Error publishing to #{self.class.name}: (#{response.status}) #{response.body}"
81
87
  end
82
- API::Message.new(@spire, response.data)
88
+ API::Message.new(@api, response.data)
83
89
  end
84
90
 
85
91
  def subscribe(name = nil)
@@ -87,9 +93,8 @@ class Spire
87
93
  unless response.status == 201
88
94
  raise "Error creating subscription for #{self.name}: (#{response.status}) #{response.body}"
89
95
  end
90
- API::Subscription.new(@spire, response.data)
96
+ API::Subscription.new(@api, response.data)
91
97
  end
92
98
  end
93
-
94
99
  end
95
100
  end
@@ -16,8 +16,8 @@ class Spire
16
16
  }.to_json,
17
17
  :headers => {
18
18
  "Authorization" => "Capability #{@capabilities["push"]}",
19
- "Accept" => @spire.mediaType("notification"),
20
- "Content-Type" => @spire.mediaType("notification")
19
+ "Accept" => @api.mediaType("notification"),
20
+ "Content-Type" => @api.mediaType("notification")
21
21
  }
22
22
  }
23
23
  end
@@ -41,9 +41,9 @@ class Spire
41
41
 
42
42
  attr_reader :url, :properties, :capabilities, :capability
43
43
 
44
- def initialize(spire, data)
45
- @spire = spire
46
- @client = spire.client
44
+ def initialize(api, data)
45
+ @api = api
46
+ @client = api.client
47
47
  @url = data["url"]
48
48
  @capabilities = data["capabilities"]
49
49
  @properties = data
@@ -92,7 +92,7 @@ class Spire
92
92
  end
93
93
 
94
94
  def schema
95
- @spire.schema[resource_name]
95
+ @api.schema[resource_name]
96
96
  end
97
97
 
98
98
  def media_type
@@ -1,6 +1,5 @@
1
1
  class Spire
2
2
  class API
3
-
4
3
  class Session
5
4
  include Requestable
6
5
 
@@ -13,7 +12,7 @@ class Spire
13
12
  :url => collection["url"],
14
13
  :headers => {
15
14
  "Authorization" => "Capability #{capability}",
16
- "Accept" => @spire.mediaType("channels"),
15
+ "Accept" => @api.mediaType("channels"),
17
16
  }
18
17
  }
19
18
  end
@@ -28,7 +27,7 @@ class Spire
28
27
  :query => {:name => name},
29
28
  :headers => {
30
29
  "Authorization" => "Capability #{capability}",
31
- "Accept" => @spire.mediaType("channels"),
30
+ "Accept" => @api.mediaType("channels"),
32
31
  }
33
32
  }
34
33
  end
@@ -49,8 +48,8 @@ class Spire
49
48
  :body => body,
50
49
  :headers => {
51
50
  "Authorization" => "Capability #{capability}",
52
- "Accept" => @spire.mediaType("channel"),
53
- "Content-Type" => @spire.mediaType("channel")
51
+ "Accept" => @api.mediaType("channel"),
52
+ "Content-Type" => @api.mediaType("channel")
54
53
  }
55
54
  }
56
55
  end
@@ -64,7 +63,7 @@ class Spire
64
63
  :url => url,
65
64
  :headers => {
66
65
  "Authorization" => "Capability #{capability}",
67
- "Accept" => @spire.mediaType("subscriptions"),
66
+ "Accept" => @api.mediaType("subscriptions"),
68
67
  }
69
68
  }
70
69
  end
@@ -91,8 +90,8 @@ class Spire
91
90
  }.to_json,
92
91
  :headers => {
93
92
  "Authorization" => "Capability #{capability}",
94
- "Accept" => @spire.mediaType("subscription"),
95
- "Content-Type" => @spire.mediaType("subscription")
93
+ "Accept" => @api.mediaType("subscription"),
94
+ "Content-Type" => @api.mediaType("subscription")
96
95
  }
97
96
  }
98
97
  end
@@ -107,7 +106,7 @@ class Spire
107
106
  :query => {:name => name},
108
107
  :headers => {
109
108
  "Authorization" => "Capability #{capability}",
110
- "Accept" => @spire.mediaType("subscriptions"),
109
+ "Accept" => @api.mediaType("subscriptions"),
111
110
  }
112
111
  }
113
112
  end
@@ -125,8 +124,8 @@ class Spire
125
124
  }.to_json,
126
125
  :headers => {
127
126
  "Authorization" => "Capability #{capability}",
128
- "Accept" => @spire.mediaType("notification"),
129
- "Content-Type" => @spire.mediaType("notification")
127
+ "Accept" => @api.mediaType("notification"),
128
+ "Content-Type" => @api.mediaType("notification")
130
129
  }
131
130
  }
132
131
  end
@@ -140,7 +139,7 @@ class Spire
140
139
  :url => url,
141
140
  :headers => {
142
141
  "Authorization" => "Capability #{capability}",
143
- "Accept" => @spire.mediaType("notifications"),
142
+ "Accept" => @api.mediaType("notifications"),
144
143
  }
145
144
  }
146
145
  end
@@ -153,7 +152,7 @@ class Spire
153
152
  :url => collection["url"],
154
153
  :headers => {
155
154
  "Authorization" => "Capability #{capability}",
156
- "Accept" => @spire.mediaType("applications"),
155
+ "Accept" => @api.mediaType("applications"),
157
156
  }
158
157
  }
159
158
  end
@@ -168,7 +167,7 @@ class Spire
168
167
  :query => {:name => name},
169
168
  :headers => {
170
169
  "Authorization" => "Capability #{capability}",
171
- "Accept" => @spire.mediaType("applications"),
170
+ "Accept" => @api.mediaType("applications"),
172
171
  }
173
172
  }
174
173
  end
@@ -181,21 +180,33 @@ class Spire
181
180
  :body => data.to_json,
182
181
  :headers => {
183
182
  "Authorization" => "Capability #{collection["capabilities"]["create"]}",
184
- "Accept" => @spire.mediaType("application"),
185
- "Content-Type" => @spire.mediaType("application")
183
+ "Accept" => @api.mediaType("application"),
184
+ "Content-Type" => @api.mediaType("application")
186
185
  }
187
186
  }
188
187
  end
189
188
 
190
189
  attr_reader :url, :resources, :schema, :capabilities, :capability
191
190
 
192
- def initialize(spire, data)
193
- @spire = spire
194
- @client = spire.client
195
- @schema = spire.schema["session"]
191
+ def initialize(api, data)
192
+ @api = api
193
+ @client = api.client
194
+ @schema = api.schema["session"]
196
195
  @url = data["url"]
197
196
  @capabilities = data["capabilities"]
198
197
  @resources = data["resources"]
198
+
199
+ @channel_error_counts = {}
200
+ @application_error_counts = {}
201
+ @subscription_error_counts = {}
202
+ @notification_error_counts = {}
203
+ end
204
+
205
+ # Returns a channel object for the named channel
206
+ # @param [String] name Name of channel returned
207
+ # @return [Channel]
208
+ def [](name)
209
+ API::Channel.new(@api, channels[name] || find_or_create_channel(name))
199
210
  end
200
211
 
201
212
  def account
@@ -203,7 +214,7 @@ class Spire
203
214
  end
204
215
 
205
216
  def account!
206
- @account = API::Account.new(@spire, @resources["account"]).get
217
+ @account = API::Account.new(@api, @resources["account"]).get
207
218
  end
208
219
 
209
220
  def create_channel(name, options={})
@@ -214,7 +225,33 @@ class Spire
214
225
  raise "Error creating Channel: (#{response.status}) #{response.body}"
215
226
  end
216
227
  properties = response.data
217
- channels[name] = API::Channel.new(@spire, properties)
228
+ channels[name] = API::Channel.new(@api, properties)
229
+ end
230
+
231
+ # Creates a channel on spire. Returns a Channel object. Note that this will
232
+ # fail with a 409 if a channel with the same name exists.
233
+ def find_or_create_channel(name)
234
+ @channel_error_counts[name] ||= 0
235
+
236
+ begin
237
+ return create_channel(name)
238
+ # TODO custom error class for Conflict, which we can
239
+ # then match here, instead of testing for error message
240
+ rescue => error
241
+ if error.message =~ /409/
242
+
243
+ # Dear retry, I love you. Affectionately, Matthew.
244
+ if channel = channels![name]
245
+ return channel
246
+ else
247
+ @channel_error_counts[name] += 1
248
+ retry unless @channel_error_counts[name] >= RETRY_CREATION_LIMIT
249
+ end
250
+
251
+ else
252
+ raise error
253
+ end
254
+ end
218
255
  end
219
256
 
220
257
  def create_subscription(subscription_name, channel_names, expiration=nil, device_token=nil, notification_name=nil, second_try=false)
@@ -239,7 +276,7 @@ class Spire
239
276
  raise "Error creating Subscription: (#{response.status}) #{response.body}"
240
277
  end
241
278
  data = response.data
242
- subscription = API::Subscription.new(@spire, data)
279
+ subscription = API::Subscription.new(@api, data)
243
280
  if subscription_name
244
281
  subscriptions[data["name"]] = subscription
245
282
  end
@@ -252,7 +289,7 @@ class Spire
252
289
  raise "Error finding application with name #{name}: (#{response.status}) #{response.body}"
253
290
  end
254
291
  properties = response.data[name]
255
- app = API::Application.new(@spire, properties)
292
+ app = API::Application.new(@api, properties)
256
293
  @applications[name] = app if @applications.is_a?(Hash)
257
294
  app
258
295
  end
@@ -264,7 +301,7 @@ class Spire
264
301
  raise "Error creating Application (#{response.status}) #{response.body}"
265
302
  end
266
303
  properties = response.data
267
- applications[name] = API::Application.new(@spire, properties)
304
+ applications[name] = API::Application.new(@api, properties)
268
305
  end
269
306
 
270
307
  def applications!
@@ -275,7 +312,7 @@ class Spire
275
312
  applications_data = response.data
276
313
  @applications = {}
277
314
  applications_data.each do |name, properties|
278
- @applications[name] = API::Application.new(@spire, properties)
315
+ @applications[name] = API::Application.new(@api, properties)
279
316
  end
280
317
  @applications
281
318
  end
@@ -290,7 +327,7 @@ class Spire
290
327
  raise "Error creating Notification: (#{response.status}) #{response.body}"
291
328
  end
292
329
  data = response.data
293
- notification = API::Notification.new(@spire, data)
330
+ notification = API::Notification.new(@api, data)
294
331
  if options[:name]
295
332
  notifications[data["name"]] = notification
296
333
  end
@@ -305,7 +342,7 @@ class Spire
305
342
  channels_data = response.data
306
343
  @channels = {}
307
344
  channels_data.each do |name, properties|
308
- @channels[name] = API::Channel.new(@spire, properties)
345
+ @channels[name] = API::Channel.new(@api, properties)
309
346
  end
310
347
  @channels
311
348
  end
@@ -325,11 +362,44 @@ class Spire
325
362
  end
326
363
  @subscriptions = {}
327
364
  response.data.each do |name, properties|
328
- @subscriptions[name] = API::Subscription.new(@spire, properties)
365
+ @subscriptions[name] = API::Subscription.new(@api, properties)
329
366
  end
330
367
  @subscriptions
331
368
  end
332
369
 
370
+ # Returns a subscription object for the given channels
371
+ # @param [String] subscription_name Name for the subscription
372
+ # @param [String] channels One or more channel names for the subscription to listen on
373
+ # @return [Subscription]
374
+ def subscribe(name, *channels)
375
+ channels.each { |channel| self.find_or_create_channel(channel) }
376
+ API::Subscription.new(@api,
377
+ subscriptions[name] || find_or_create_subscription(name, *channels)
378
+ )
379
+ end
380
+
381
+ def find_or_create_subscription(subscription_name, *channels)
382
+ @subscription_error_counts[subscription_name] ||= 0
383
+ begin
384
+ return create_subscription(subscription_name, channels)
385
+ rescue => error
386
+ if error.message =~ /409/
387
+
388
+ if subscription = subscriptions![subscription_name]
389
+ return subscription
390
+ else
391
+ @subscription_error_counts[subscription_name] += 1
392
+ retry unless @subscription_error_counts >= RETRY_CREATION_LIMIT
393
+ end
394
+
395
+ else
396
+ raise error
397
+ end
398
+ end
399
+ end
400
+
401
+ alias :subscription :subscribe #For compatibility with other clients
402
+
333
403
  def notifications
334
404
  @notifications ||= notifications!
335
405
  end
@@ -341,12 +411,62 @@ class Spire
341
411
  end
342
412
  @notifications = {}
343
413
  response.data.each do |name, properties|
344
- @notifications[name] = API::Notification.new(@spire, properties)
414
+ @notifications[name] = API::Notification.new(@api, properties)
345
415
  end
346
416
  @notifications
347
417
  end
348
418
 
349
- end
419
+ def notification(name, mode="development")
420
+ Notification.new(@api,
421
+ @notifications[name] || find_or_create_notification(name, ssl_cert)
422
+ )
423
+ end
424
+
425
+ def find_or_create_notification(notification_name, mode)
426
+ @notification_error_counts[notification_name] ||= 0
427
+ begin
428
+ return create_notification(
429
+ :name => notification_name,
430
+ :mode => mode
431
+ )
432
+ rescue => error
433
+ if error.message =~ /409/
434
+
435
+ if notification = notification![notification_name]
436
+ return notification
437
+ else
438
+ @notification_error_counts[notification_name] += 1
439
+ retry unless @notification_error_counts >= RETRY_CREATION_LIMIT
440
+ end
441
+
442
+ else
443
+ raise error
444
+ end
445
+ end
446
+ end
350
447
 
448
+ # Creates an application on spire. Returns an Application object. Will retry on a 409.
449
+ # @param [String] Name of the application to find/create
450
+ def find_or_create_application(name)
451
+ @application_error_counts[name] ||= 0
452
+ begin
453
+ return create_application(name)
454
+ # TODO custom error class for Conflict, which we can
455
+ # then match here, instead of testing for error message
456
+ rescue => error
457
+ if error.message =~ /409/
458
+ # Dear retry, I love you. Affectionately, Matthew.
459
+ if application = applications![name]
460
+ return application
461
+ else
462
+ @application_error_counts[name] += 1
463
+ retry unless @application_error_counts[name] >= RETRY_CREATION_LIMIT
464
+ end
465
+ else
466
+ raise error
467
+ end
468
+ end
469
+ end
470
+ end
351
471
  end
352
472
  end
@@ -1,6 +1,18 @@
1
1
  class Spire
2
2
  class API
3
3
 
4
+ # The subscription class represents a read connection to a Spire channel
5
+ #
6
+ # You can get a subscription by calling subscribe on a spire object with the name of the channel or
7
+ # by calling subscribe on a channel object
8
+ #
9
+ # * spire = Spire.new
10
+ # * spire.start("your api secret")
11
+ # *THEN*
12
+ # * subscription = spire.subscribe("subscription name", "channel name")
13
+ # *OR*
14
+ # * channel = spire["channel name"]
15
+ # * subscription = channel.subscribe("subscription name")
4
16
  class Subscription < Resource
5
17
 
6
18
  attr_accessor :last
@@ -23,7 +35,7 @@ class Spire
23
35
  },
24
36
  :headers => {
25
37
  "Authorization" => "Capability #{@capabilities["events"]}",
26
- "Accept" => @spire.mediaType("events")
38
+ "Accept" => @api.mediaType("events")
27
39
  }
28
40
  }
29
41
  end
@@ -36,20 +48,75 @@ class Spire
36
48
  else
37
49
  @listeners = { }
38
50
  EVENT_TYPES.each do |type|
39
- @listeners[type] = []
51
+ @listeners[type] = {}
40
52
  end
41
53
  @listeners
42
54
  end
43
55
  end
44
56
 
45
- def add_listener(type="message", &block)
57
+ # provided named listeners, threading, and a
58
+ # stop_listening method.
59
+ def add_listener(type, listener_name = nil, &block)
60
+ type ||= ""
46
61
  type.downcase!
47
62
  if !EVENT_TYPES.include?(type)
48
63
  throw "Listener type must be one of #{EVENT_TYPES}"
49
64
  end
50
65
 
51
- listeners[type] << block
52
- block
66
+ raise ArgumentError unless block_given?
67
+ listener_name ||= generate_listener_name
68
+ listener = wrap_listener(&block)
69
+ listeners[type][listener_name] = listener
70
+ listener_name
71
+ end
72
+
73
+ def remove_listener(type, arg)
74
+ type ||= ""
75
+ type.downcase!
76
+ if !EVENT_TYPES.include?(type)
77
+ throw "Listener type must be one of #{EVENT_TYPES}"
78
+ end
79
+
80
+ if arg.is_a? String
81
+ listener = listeners[type].delete(arg)
82
+ else
83
+ listener_name, _listener = listeners.detect {|k,v| v == arg }
84
+ listener = listeners[type].delete(listener_name)
85
+ end
86
+
87
+ if listener
88
+ listeners[type].delete(listener)
89
+ end
90
+ end
91
+
92
+ def wrap_listener(&block)
93
+ lambda do |message|
94
+ Thread.new do
95
+ # Messages received after a call to stop_listening
96
+ # will not be processed.
97
+ yield message if @listening
98
+ end
99
+ end
100
+ end
101
+
102
+ def generate_listener_name
103
+ listener_name = nil
104
+ while !listener_name
105
+ new_name = "Listener-#{rand(9999999)}"
106
+ listener_name = new_name unless listeners.has_key?(new_name)
107
+ end
108
+ listener_name
109
+ end
110
+
111
+ def start_listening(options={})
112
+ @listening = true
113
+ Thread.new do
114
+ long_poll(options) while @listening
115
+ end
116
+ end
117
+
118
+ def stop_listening
119
+ @listening = false
53
120
  end
54
121
 
55
122
  def retrieve_events(options={})
@@ -70,10 +137,10 @@ class Spire
70
137
  response.data[type_pl].each do |event|
71
138
  klass_name = type.capitalize
72
139
  klass = API.const_get(klass_name)
73
- event_obj = klass.new(@spire, event)
140
+ event_obj = klass.new(@api, event)
74
141
  event_hash[type_pl.to_sym].push(event_obj)
75
142
 
76
- listeners[type].each do |listener|
143
+ listeners[type].each_value do |listener|
77
144
  listener.call(event_obj)
78
145
  end
79
146
  end
@@ -93,13 +160,6 @@ class Spire
93
160
  options[:last] = @last if @last
94
161
  retrieve_events(options)
95
162
  end
96
-
97
- def listen(options={})
98
- loop do
99
- break unless yield(long_poll(options))
100
- end
101
- end
102
-
103
163
  end
104
164
  end
105
165
  end
@@ -13,22 +13,9 @@ class Spire
13
13
  def initialize(url="https://api.spire.io")
14
14
  @api = Spire::API.new(url)
15
15
  @url = url
16
- @channel_error_counts = {}
17
- @application_error_counts = {}
18
- @subscription_error_counts = {}
19
- @notification_error_counts = {}
20
- discover
21
- end
22
-
23
- def secret
24
- @session.resources["account"]["secret"]
25
- end
26
-
27
- def discover
28
16
  @api.discover
29
- self
30
17
  end
31
-
18
+
32
19
  def start(secret)
33
20
  @session = @api.create_session(secret)
34
21
  self
@@ -52,301 +39,4 @@ class Spire
52
39
  def password_reset_request(email)
53
40
  @api.password_reset_request(email)
54
41
  end
55
-
56
-
57
- # Deletes the currently authenticated account
58
- def delete_account
59
- @session.account.delete
60
- end
61
-
62
- # Updates the current account with the new account information
63
- # See Spire docs for available settings
64
- def update(info)
65
- @session.account.update(info)
66
- self
67
- end
68
-
69
- # Returns a channel object for the named channel
70
- # @param [String] name Name of channel returned
71
- # @return [Channel]
72
- def [](name)
73
- Channel.new(self, channels[name] || find_or_create_channel(name))
74
- end
75
-
76
- def channels
77
- @session.channels
78
- end
79
-
80
- def channels!
81
- @session.channels!
82
- end
83
-
84
- # Creates a channel on spire. Returns a Channel object. Note that this will
85
- # fail with a 409 if a channel with the same name exists.
86
- def find_or_create_channel(name)
87
- @channel_error_counts[name] ||= 0
88
-
89
- begin
90
- return @session.create_channel(name)
91
- # TODO custom error class for Conflict, which we can
92
- # then match here, instead of testing for error message
93
- rescue => error
94
- if error.message =~ /409/
95
-
96
- # Dear retry, I love you. Affectionately, Matthew.
97
- if channel = @session.channels![name]
98
- return channel
99
- else
100
- @channel_error_counts[name] += 1
101
- retry unless @channel_error_counts[name] >= RETRY_CREATION_LIMIT
102
- end
103
-
104
- else
105
- raise error
106
- end
107
- end
108
- end
109
-
110
- # Returns a subscription object for the given channels
111
- # @param [String] subscription_name Name for the subscription
112
- # @param [String] channels One or more channel names for the subscription to listen on
113
- # @return [Subscription]
114
- def subscribe(name, *channels)
115
- channels.each { |channel| self.find_or_create_channel(channel) }
116
- Subscription.new(
117
- @session.subscriptions[name] || find_or_create_subscription(name, *channels)
118
- )
119
- end
120
-
121
- def find_or_create_subscription(subscription_name, *channels)
122
- @subscription_error_counts[subscription_name] ||= 0
123
- begin
124
- return @session.create_subscription(subscription_name, channels)
125
- rescue => error
126
- if error.message =~ /409/
127
-
128
- if subscription = @session.subscriptions![subscription_name]
129
- return subscription
130
- else
131
- @subscription_error_counts[subscription_name] += 1
132
- retry unless @subscription_error_counts >= RETRY_CREATION_LIMIT
133
- end
134
-
135
- else
136
- raise error
137
- end
138
- end
139
- end
140
-
141
- alias :subscription :subscribe #For compatibility with other clients
142
-
143
- #Returns an array of subscription objects for all of this account's subscriptions
144
- #@return [Array]
145
- def subscriptions
146
- @session.subscriptions.values
147
- end
148
-
149
-
150
- def notification(name, mode="development")
151
- Notification.new(
152
- @session.notifications[name] || find_or_create_notification(name, ssl_cert)
153
- )
154
- end
155
-
156
- def find_or_create_notification(notification_name, mode)
157
- @notification_error_counts[notification_name] ||= 0
158
- begin
159
- return @session.create_notification(
160
- :name => notification_name,
161
- :mode => mode
162
- )
163
- rescue => error
164
- if error.message =~ /409/
165
-
166
- if notification = @session.notification![notification_name]
167
- return notification
168
- else
169
- @notification_error_counts[notification_name] += 1
170
- retry unless @notification_error_counts >= RETRY_CREATION_LIMIT
171
- end
172
-
173
- else
174
- raise error
175
- end
176
- end
177
- end
178
-
179
-
180
- def applications
181
- @session.applications
182
- end
183
-
184
- def applications!
185
- @session.applications!
186
- end
187
-
188
- # Creates an application on spire. Returns an Application object. Will retry on a 409.
189
- # @param [String] Name of the application to find/create
190
- def find_or_create_application(name)
191
- @application_error_counts[name] ||= 0
192
- begin
193
- return @session.create_application(name)
194
- # TODO custom error class for Conflict, which we can
195
- # then match here, instead of testing for error message
196
- rescue => error
197
- if error.message =~ /409/
198
- # Dear retry, I love you. Affectionately, Matthew.
199
- if application = @session.applications![name]
200
- return application
201
- else
202
- @application_error_counts[name] += 1
203
- retry unless @application_error_counts[name] >= RETRY_CREATION_LIMIT
204
- end
205
- else
206
- raise error
207
- end
208
- end
209
- end
210
-
211
- # Returns a billing object than contains a list of all the plans available
212
- # @param [String] info optional object description
213
- # @return [Billing]
214
- def billing
215
- @api.billing
216
- end
217
-
218
- # Updates and subscribe the account to a billing plan
219
- # @param [Object] info data containing billing description
220
- # @return [Account]
221
- def billing_subscription(info)
222
- @session.account.billing_subscription(info)
223
- self
224
- end
225
-
226
-
227
- # Object representing a Spire channel
228
- #
229
- # You can get a channel object by calling [] on a Spire object
230
- # * spire = Spire.new
231
- # * spire.start("your api secret")
232
- # * channel = spire["channel name"]
233
- class Channel < SimpleDelegator
234
- def initialize(spire, channel)
235
- super(channel)
236
- @spire = spire
237
- end
238
- # Obtain a subscription for the channel
239
- # @param [String] subscription_name Name of the subscription
240
- # @return [Subscription]
241
- def subscribe(subscription_name = nil)
242
- @spire.subscribe(subscription_name, properties["name"])
243
- end
244
-
245
- # this is required because Delegator's method_missing relies
246
- # on the object having a method defined, but in this case
247
- # the API::Channel is also using method_missing
248
- def name
249
- __getobj__.name
250
- end
251
- end
252
-
253
- # The subscription class represents a read connection to a Spire channel
254
- #
255
- # You can get a subscription by calling subscribe on a spire object with the name of the channel or
256
- # by calling subscribe on a channel object
257
- #
258
- # * spire = Spire.new
259
- # * spire.start("your api secret")
260
- # *THEN*
261
- # * subscription = spire.subscribe("subscription name", "channel name")
262
- # *OR*
263
- # * channel = spire["channel name"]
264
- # * subscription = channel.subscribe("subscription name")
265
- class Subscription < SimpleDelegator
266
-
267
- # this is required because Delegator's method_missing relies
268
- # on the object having a method defined, but in this case
269
- # the API::Subscription is also using method_missing
270
- def name
271
- __getobj__.name
272
- end
273
-
274
- # misnamed method, here for backompat. Should be something like #get_messages,
275
- # because it only makes one request.
276
- def listen(options={})
277
- long_poll(options)
278
- end
279
-
280
- # wraps the underlying Subscription#add_listener to
281
- # provided named listeners, threading, and a
282
- # stop_listening method.
283
- def add_listener(listener_name = nil, &block)
284
- raise ArgumentError unless block_given?
285
- listener_name ||= generate_listener_name
286
- listener = wrap_listener(&block)
287
- listeners[listener_name] = listener
288
- __getobj__.add_listener("message", &listener)
289
- end
290
-
291
- def remove_listener(arg)
292
- if arg.is_a? String
293
- listener = listeners.delete(arg)
294
- else
295
- listener_name, _listener = listeners.detect {|k,v| v == arg }
296
- listener = listeners.delete(listener_name)
297
- end
298
-
299
- if listener
300
- __getobj__.listeners["message"].delete(listener)
301
- end
302
- end
303
-
304
- def wrap_listener(&block)
305
- lambda do |message|
306
- Thread.new do
307
- # Messages received after a call to stop_listening
308
- # will not be processed.
309
- yield message["content"] if @listening
310
- end
311
- end
312
- end
313
-
314
- def listeners
315
- @listeners ||= {}
316
- end
317
-
318
- def generate_listener_name
319
- listener_name = nil
320
- while !listener_name
321
- new_name = "Listener-#{rand(9999999)}"
322
- listener_name = new_name unless listeners.has_key?(new_name)
323
- end
324
- listener_name
325
- end
326
-
327
- def start_listening(options={})
328
- @listening = true
329
- Thread.new do
330
- long_poll(options) while @listening
331
- end
332
- end
333
-
334
- def stop_listening
335
- @listening = false
336
- end
337
-
338
- end
339
-
340
- class Notification < SimpleDelegator
341
-
342
- # this is required because Delegator's method_missing relies
343
- # on the object having a method defined, but in this case
344
- # the API::Subscription is also using method_missing
345
- def name
346
- __getobj__.name
347
- end
348
-
349
-
350
- end
351
-
352
42
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spire_io
3
3
  version: !ruby/object:Gem::Version
4
- hash: 29
4
+ hash: 27
5
5
  prerelease: false
6
6
  segments:
7
7
  - 1
8
8
  - 2
9
- - 1
10
- version: 1.2.1
9
+ - 2
10
+ version: 1.2.2
11
11
  platform: ruby
12
12
  authors:
13
13
  - Dan Yoder
@@ -17,7 +17,7 @@ autorequire:
17
17
  bindir: bin
18
18
  cert_chain: []
19
19
 
20
- date: 2012-05-22 00:00:00 -07:00
20
+ date: 2012-05-24 00:00:00 -07:00
21
21
  default_executable:
22
22
  dependencies:
23
23
  - !ruby/object:Gem::Dependency