spire_io 1.2.1 → 1.2.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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