rapns 0.2.3 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +35 -10
- data/lib/generators/rapns_generator.rb +9 -1
- data/lib/generators/templates/create_rapns_feedback.rb +15 -0
- data/lib/generators/templates/rapns.yml +22 -9
- data/lib/rapns/daemon/configuration.rb +32 -14
- data/lib/rapns/daemon/connection.rb +35 -50
- data/lib/rapns/daemon/delivery_handler.rb +65 -15
- data/lib/rapns/daemon/delivery_handler_pool.rb +1 -5
- data/lib/rapns/daemon/delivery_queue.rb +10 -27
- data/lib/rapns/daemon/feedback_receiver.rb +57 -0
- data/lib/rapns/daemon/feeder.rb +9 -7
- data/lib/rapns/daemon/interruptible_sleep.rb +14 -0
- data/lib/rapns/daemon/pool.rb +0 -5
- data/lib/rapns/daemon.rb +9 -9
- data/lib/rapns/device_token_format_validator.rb +10 -0
- data/lib/rapns/feedback.rb +10 -0
- data/lib/rapns/notification.rb +15 -1
- data/lib/rapns/version.rb +1 -1
- data/lib/rapns.rb +3 -1
- data/spec/rapns/daemon/certificate_spec.rb +6 -0
- data/spec/rapns/daemon/configuration_spec.rb +124 -40
- data/spec/rapns/daemon/connection_spec.rb +81 -129
- data/spec/rapns/daemon/delivery_handler_pool_spec.rb +1 -6
- data/spec/rapns/daemon/delivery_handler_spec.rb +117 -30
- data/spec/rapns/daemon/delivery_queue_spec.rb +29 -0
- data/spec/rapns/daemon/feedback_receiver_spec.rb +86 -0
- data/spec/rapns/daemon/feeder_spec.rb +25 -9
- data/spec/rapns/daemon/interruptible_sleep_spec.rb +32 -0
- data/spec/rapns/daemon/logger_spec.rb +34 -14
- data/spec/rapns/daemon_spec.rb +34 -31
- data/spec/rapns/feedback_spec.rb +12 -0
- data/spec/rapns/notification_spec.rb +5 -0
- data/spec/spec_helper.rb +5 -2
- metadata +16 -5
- data/lib/rapns/daemon/connection_pool.rb +0 -31
- data/spec/rapns/daemon/connection_pool_spec.rb +0 -40
@@ -0,0 +1,14 @@
|
|
1
|
+
module Rapns
|
2
|
+
module Daemon
|
3
|
+
module InterruptibleSleep
|
4
|
+
def interruptible_sleep(seconds)
|
5
|
+
@_sleep_check, @_sleep_interrupt = IO.pipe
|
6
|
+
IO.select([@_sleep_check], nil, nil, seconds)
|
7
|
+
end
|
8
|
+
|
9
|
+
def interrupt_sleep
|
10
|
+
@_sleep_interrupt.close if @_sleep_interrupt
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/rapns/daemon/pool.rb
CHANGED
@@ -15,8 +15,6 @@ module Rapns
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def drain
|
18
|
-
drain_started
|
19
|
-
|
20
18
|
while !@queue.empty?
|
21
19
|
object = @queue.pop
|
22
20
|
object_removed_from_pool(object)
|
@@ -33,9 +31,6 @@ module Rapns
|
|
33
31
|
|
34
32
|
def object_removed_from_pool(object)
|
35
33
|
end
|
36
|
-
|
37
|
-
def drain_started
|
38
|
-
end
|
39
34
|
end
|
40
35
|
end
|
41
36
|
end
|
data/lib/rapns/daemon.rb
CHANGED
@@ -2,23 +2,24 @@ require 'thread'
|
|
2
2
|
require 'socket'
|
3
3
|
require 'pathname'
|
4
4
|
|
5
|
+
require 'rapns/daemon/interruptible_sleep'
|
5
6
|
require 'rapns/daemon/configuration'
|
6
7
|
require 'rapns/daemon/certificate'
|
7
8
|
require 'rapns/daemon/delivery_error'
|
8
9
|
require 'rapns/daemon/pool'
|
9
|
-
require 'rapns/daemon/connection_pool'
|
10
10
|
require 'rapns/daemon/connection'
|
11
11
|
require 'rapns/daemon/delivery_queue'
|
12
12
|
require 'rapns/daemon/delivery_handler'
|
13
13
|
require 'rapns/daemon/delivery_handler_pool'
|
14
|
+
require 'rapns/daemon/feedback_receiver'
|
14
15
|
require 'rapns/daemon/feeder'
|
15
16
|
require 'rapns/daemon/logger'
|
16
17
|
|
17
18
|
module Rapns
|
18
19
|
module Daemon
|
19
20
|
class << self
|
20
|
-
attr_accessor :logger, :configuration, :certificate,
|
21
|
-
:delivery_handler_pool, :foreground
|
21
|
+
attr_accessor :logger, :configuration, :certificate,
|
22
|
+
:delivery_queue, :delivery_handler_pool, :foreground
|
22
23
|
alias_method :foreground?, :foreground
|
23
24
|
end
|
24
25
|
|
@@ -34,19 +35,18 @@ module Rapns
|
|
34
35
|
self.certificate = Certificate.new(configuration.certificate)
|
35
36
|
certificate.load
|
36
37
|
|
37
|
-
self.delivery_queue = DeliveryQueue.new
|
38
|
+
self.delivery_queue = DeliveryQueue.new
|
38
39
|
|
39
40
|
daemonize unless foreground?
|
40
41
|
|
41
42
|
write_pid_file
|
42
43
|
|
43
|
-
self.delivery_handler_pool = DeliveryHandlerPool.new(configuration.connections)
|
44
|
+
self.delivery_handler_pool = DeliveryHandlerPool.new(configuration.push.connections)
|
44
45
|
delivery_handler_pool.populate
|
45
46
|
|
46
|
-
self.connection_pool = ConnectionPool.new(configuration.connections)
|
47
|
-
connection_pool.populate
|
48
|
-
|
49
47
|
logger.info('Ready')
|
48
|
+
|
49
|
+
FeedbackReceiver.start
|
50
50
|
Feeder.start(foreground?)
|
51
51
|
end
|
52
52
|
|
@@ -70,9 +70,9 @@ module Rapns
|
|
70
70
|
|
71
71
|
def self.shutdown
|
72
72
|
puts "\nShutting down..."
|
73
|
+
Rapns::Daemon::FeedbackReceiver.stop
|
73
74
|
Rapns::Daemon::Feeder.stop
|
74
75
|
Rapns::Daemon.delivery_handler_pool.drain if Rapns::Daemon.delivery_handler_pool
|
75
|
-
Rapns::Daemon.connection_pool.drain if Rapns::Daemon.connection_pool
|
76
76
|
delete_pid_file
|
77
77
|
end
|
78
78
|
|
data/lib/rapns/notification.rb
CHANGED
@@ -2,10 +2,11 @@ module Rapns
|
|
2
2
|
class Notification < ActiveRecord::Base
|
3
3
|
set_table_name "rapns_notifications"
|
4
4
|
|
5
|
-
validates :device_token, :presence => true
|
5
|
+
validates :device_token, :presence => true
|
6
6
|
validates :badge, :numericality => true, :allow_nil => true
|
7
7
|
validates :expiry, :numericality => true, :presence => true
|
8
8
|
|
9
|
+
validates_with Rapns::DeviceTokenFormatValidator
|
9
10
|
validates_with Rapns::BinaryNotificationValidator
|
10
11
|
|
11
12
|
scope :ready_for_delivery, lambda { where(:delivered => false, :failed => false).merge(where("deliver_after IS NULL") | where("deliver_after < ?", Time.now)) }
|
@@ -14,6 +15,19 @@ module Rapns
|
|
14
15
|
write_attribute(:device_token, token.delete(" <>")) if !token.nil?
|
15
16
|
end
|
16
17
|
|
18
|
+
def alert=(alert)
|
19
|
+
if alert.is_a?(Hash)
|
20
|
+
write_attribute(:alert, ActiveSupport::JSON.encode(alert))
|
21
|
+
else
|
22
|
+
write_attribute(:alert, alert)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def alert
|
27
|
+
string_or_json = read_attribute(:alert)
|
28
|
+
ActiveSupport::JSON.decode(string_or_json) rescue string_or_json
|
29
|
+
end
|
30
|
+
|
17
31
|
def attributes_for_device=(attrs)
|
18
32
|
raise ArgumentError, "attributes_for_device must be a Hash" if !attrs.is_a?(Hash)
|
19
33
|
write_attribute(:attributes_for_device, ActiveSupport::JSON.encode(attrs))
|
data/lib/rapns/version.rb
CHANGED
data/lib/rapns.rb
CHANGED
@@ -1,6 +1,12 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
3
|
describe Rapns::Daemon::Certificate do
|
4
|
+
it 'reads the certificate from the given path' do
|
5
|
+
File.stub(:exists? => true)
|
6
|
+
File.should_receive(:read).with("/dir/development.pem")
|
7
|
+
cert = Rapns::Daemon::Certificate.new("/dir/development.pem")
|
8
|
+
cert.load
|
9
|
+
end
|
4
10
|
|
5
11
|
it "should raise an error if the .pem file does not exist" do
|
6
12
|
cert = Rapns::Daemon::Certificate.new("/tmp/rapns-missing.pem")
|
@@ -4,9 +4,46 @@ describe Rapns::Daemon::Configuration do
|
|
4
4
|
module Rails
|
5
5
|
end
|
6
6
|
|
7
|
+
let(:config) do
|
8
|
+
{
|
9
|
+
"airbrake_notify" => false,
|
10
|
+
"certificate" => "production.pem",
|
11
|
+
"certificate_password" => "abc123",
|
12
|
+
"pid_file" => "rapns.pid",
|
13
|
+
"push" => {
|
14
|
+
"port" => 123,
|
15
|
+
"host" => "localhost",
|
16
|
+
"poll" => 4,
|
17
|
+
"connections" => 6
|
18
|
+
},
|
19
|
+
"feedback" => {
|
20
|
+
"port" => 123,
|
21
|
+
"host" => "localhost",
|
22
|
+
"poll" => 30,
|
23
|
+
}
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
7
27
|
before do
|
8
28
|
Rails.stub(:root).and_return("/rails_root")
|
9
|
-
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'opens the config from the given path' do
|
32
|
+
YAML.stub(:load => {"production" => config})
|
33
|
+
fd = stub(:read => nil)
|
34
|
+
File.should_receive(:open).with("/tmp/rapns-non-existant-file").and_yield(fd)
|
35
|
+
config = Rapns::Daemon::Configuration.new("production", "/tmp/rapns-non-existant-file")
|
36
|
+
config.stub(:ensure_config_exists)
|
37
|
+
config.load
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'reads the config as YAML' do
|
41
|
+
YAML.should_receive(:load).and_return({"production" => config})
|
42
|
+
fd = stub(:read => nil)
|
43
|
+
File.stub(:open).and_yield(fd)
|
44
|
+
config = Rapns::Daemon::Configuration.new("production", "/tmp/rapns-non-existant-file")
|
45
|
+
config.stub(:ensure_config_exists)
|
46
|
+
config.load
|
10
47
|
end
|
11
48
|
|
12
49
|
it "should raise an error if the configuration file does not exist" do
|
@@ -16,131 +53,178 @@ describe Rapns::Daemon::Configuration do
|
|
16
53
|
it "should raise an error if the environment is not configured" do
|
17
54
|
configuration = Rapns::Daemon::Configuration.new("development", "/some/config.yml")
|
18
55
|
configuration.stub(:read_config).and_return({"production" => {}})
|
19
|
-
expect { configuration.load
|
56
|
+
expect { configuration.load }.to raise_error(Rapns::ConfigurationError, "Configuration for environment 'development' not defined in /some/config.yml")
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should raise an error if the push host is not configured" do
|
60
|
+
configuration = Rapns::Daemon::Configuration.new("production", "/some/config.yml")
|
61
|
+
config["push"]["host"] = nil
|
62
|
+
configuration.stub(:read_config).and_return({"production" => config})
|
63
|
+
expect { configuration.load }.to raise_error(Rapns::ConfigurationError, "'push.host' not defined for environment 'production' in /some/config.yml. You may need to run 'rails g rapns' after updating.")
|
20
64
|
end
|
21
65
|
|
22
|
-
it "should raise an error if the
|
66
|
+
it "should raise an error if the push port is not configured" do
|
23
67
|
configuration = Rapns::Daemon::Configuration.new("production", "/some/config.yml")
|
24
|
-
|
25
|
-
|
68
|
+
config["push"]["port"] = nil
|
69
|
+
configuration.stub(:read_config).and_return({"production" => config})
|
70
|
+
expect { configuration.load }.to raise_error(Rapns::ConfigurationError, "'push.port' not defined for environment 'production' in /some/config.yml. You may need to run 'rails g rapns' after updating.")
|
26
71
|
end
|
27
72
|
|
28
|
-
it "should raise an error if the
|
73
|
+
it "should raise an error if the feedback host is not configured" do
|
29
74
|
configuration = Rapns::Daemon::Configuration.new("production", "/some/config.yml")
|
30
|
-
|
31
|
-
|
75
|
+
config["feedback"]["host"] = nil
|
76
|
+
configuration.stub(:read_config).and_return({"production" => config})
|
77
|
+
expect { configuration.load }.to raise_error(Rapns::ConfigurationError, "'feedback.host' not defined for environment 'production' in /some/config.yml. You may need to run 'rails g rapns' after updating.")
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should raise an error if the feedback port is not configured" do
|
81
|
+
configuration = Rapns::Daemon::Configuration.new("production", "/some/config.yml")
|
82
|
+
config["feedback"]["port"] = nil
|
83
|
+
configuration.stub(:read_config).and_return({"production" => config})
|
84
|
+
expect { configuration.load }.to raise_error(Rapns::ConfigurationError, "'feedback.port' not defined for environment 'production' in /some/config.yml. You may need to run 'rails g rapns' after updating.")
|
32
85
|
end
|
33
86
|
|
34
87
|
it "should raise an error if the certificate is not configured" do
|
35
88
|
configuration = Rapns::Daemon::Configuration.new("production", "/some/config.yml")
|
36
|
-
configuration.stub(:read_config).and_return({"production" =>
|
37
|
-
expect { configuration.load }.to raise_error(Rapns::ConfigurationError, "'certificate' not defined for environment 'production' in /some/config.yml")
|
89
|
+
configuration.stub(:read_config).and_return({"production" => config.except("certificate")})
|
90
|
+
expect { configuration.load }.to raise_error(Rapns::ConfigurationError, "'certificate' not defined for environment 'production' in /some/config.yml. You may need to run 'rails g rapns' after updating.")
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should set the push host" do
|
94
|
+
configuration = Rapns::Daemon::Configuration.new("production", "/some/config.yml")
|
95
|
+
configuration.stub(:read_config).and_return({"production" => config})
|
96
|
+
configuration.load
|
97
|
+
configuration.push.host.should == "localhost"
|
38
98
|
end
|
39
99
|
|
40
|
-
it "should set the
|
100
|
+
it "should set the push port" do
|
41
101
|
configuration = Rapns::Daemon::Configuration.new("production", "/some/config.yml")
|
42
|
-
configuration.stub(:read_config).and_return({"production" =>
|
102
|
+
configuration.stub(:read_config).and_return({"production" => config})
|
43
103
|
configuration.load
|
44
|
-
configuration.
|
104
|
+
configuration.push.port.should == 123
|
45
105
|
end
|
46
106
|
|
47
|
-
it "should set the port" do
|
107
|
+
it "should set the feedback port" do
|
48
108
|
configuration = Rapns::Daemon::Configuration.new("production", "/some/config.yml")
|
49
|
-
configuration.stub(:read_config).and_return({"production" =>
|
109
|
+
configuration.stub(:read_config).and_return({"production" => config})
|
50
110
|
configuration.load
|
51
|
-
configuration.port.should == 123
|
111
|
+
configuration.feedback.port.should == 123
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should set the feedback host" do
|
115
|
+
configuration = Rapns::Daemon::Configuration.new("production", "/some/config.yml")
|
116
|
+
configuration.stub(:read_config).and_return({"production" => config})
|
117
|
+
configuration.load
|
118
|
+
configuration.feedback.host.should == "localhost"
|
52
119
|
end
|
53
120
|
|
54
121
|
it "should set the airbrake notify flag" do
|
55
122
|
configuration = Rapns::Daemon::Configuration.new("production", "/some/config.yml")
|
56
|
-
configuration.stub(:read_config).and_return({"production" =>
|
123
|
+
configuration.stub(:read_config).and_return({"production" => config})
|
57
124
|
configuration.load
|
58
125
|
configuration.airbrake_notify?.should == false
|
59
126
|
end
|
60
127
|
|
61
128
|
it "should default the airbrake notify flag to true if not set" do
|
62
129
|
configuration = Rapns::Daemon::Configuration.new("production", "/some/config.yml")
|
63
|
-
configuration.stub(:read_config).and_return({"production" =>
|
130
|
+
configuration.stub(:read_config).and_return({"production" => config.except("airbrake_notify")})
|
64
131
|
configuration.load
|
65
132
|
configuration.airbrake_notify?.should == true
|
66
133
|
end
|
67
134
|
|
68
|
-
it "should set the poll frequency" do
|
135
|
+
it "should set the push poll frequency" do
|
136
|
+
configuration = Rapns::Daemon::Configuration.new("production", "/some/config.yml")
|
137
|
+
configuration.stub(:read_config).and_return({"production" => config})
|
138
|
+
configuration.load
|
139
|
+
configuration.push.poll.should == 4
|
140
|
+
end
|
141
|
+
|
142
|
+
it "should set the feedback poll frequency" do
|
143
|
+
configuration = Rapns::Daemon::Configuration.new("production", "/some/config.yml")
|
144
|
+
configuration.stub(:read_config).and_return({"production" => config})
|
145
|
+
configuration.load
|
146
|
+
configuration.feedback.poll.should == 30
|
147
|
+
end
|
148
|
+
|
149
|
+
it "should default the push poll frequency to 2 if not set" do
|
69
150
|
configuration = Rapns::Daemon::Configuration.new("production", "/some/config.yml")
|
70
|
-
|
151
|
+
config["push"]["poll"] = nil
|
152
|
+
configuration.stub(:read_config).and_return({"production" => config})
|
71
153
|
configuration.load
|
72
|
-
configuration.poll.should ==
|
154
|
+
configuration.push.poll.should == 2
|
73
155
|
end
|
74
156
|
|
75
|
-
it "should default the poll frequency to
|
157
|
+
it "should default the feedback poll frequency to 60 if not set" do
|
76
158
|
configuration = Rapns::Daemon::Configuration.new("production", "/some/config.yml")
|
77
|
-
|
159
|
+
config["feedback"]["poll"] = nil
|
160
|
+
configuration.stub(:read_config).and_return({"production" => config})
|
78
161
|
configuration.load
|
79
|
-
configuration.poll.should ==
|
162
|
+
configuration.feedback.poll.should == 60
|
80
163
|
end
|
81
164
|
|
82
|
-
it "should set the number of connections" do
|
165
|
+
it "should set the number of push connections" do
|
83
166
|
configuration = Rapns::Daemon::Configuration.new("production", "/some/config.yml")
|
84
|
-
configuration.stub(:read_config).and_return({"production" =>
|
167
|
+
configuration.stub(:read_config).and_return({"production" => config})
|
85
168
|
configuration.load
|
86
|
-
configuration.connections.should == 6
|
169
|
+
configuration.push.connections.should == 6
|
87
170
|
end
|
88
171
|
|
89
|
-
it "should default the number of connections to 3 if not set" do
|
172
|
+
it "should default the number of push connections to 3 if not set" do
|
90
173
|
configuration = Rapns::Daemon::Configuration.new("production", "/some/config.yml")
|
91
|
-
|
174
|
+
config["push"]["connections"] = nil
|
175
|
+
configuration.stub(:read_config).and_return({"production" => config})
|
92
176
|
configuration.load
|
93
|
-
configuration.connections.should == 3
|
177
|
+
configuration.push.connections.should == 3
|
94
178
|
end
|
95
179
|
|
96
180
|
it "should set the certificate password" do
|
97
181
|
configuration = Rapns::Daemon::Configuration.new("production", "/some/config.yml")
|
98
|
-
configuration.stub(:read_config).and_return({"production" =>
|
182
|
+
configuration.stub(:read_config).and_return({"production" => config})
|
99
183
|
configuration.load
|
100
184
|
configuration.certificate_password.should == "abc123"
|
101
185
|
end
|
102
186
|
|
103
187
|
it "should set the certificate password to a blank string if it is not configured" do
|
104
188
|
configuration = Rapns::Daemon::Configuration.new("production", "/some/config.yml")
|
105
|
-
configuration.stub(:read_config).and_return({"production" =>
|
189
|
+
configuration.stub(:read_config).and_return({"production" => config.except("certificate_password")})
|
106
190
|
configuration.load
|
107
191
|
configuration.certificate_password.should == ""
|
108
192
|
end
|
109
193
|
|
110
194
|
it "should set the certificate, with absolute path" do
|
111
195
|
configuration = Rapns::Daemon::Configuration.new("production", "/some/config.yml")
|
112
|
-
configuration.stub(:read_config).and_return({"production" =>
|
196
|
+
configuration.stub(:read_config).and_return({"production" => config})
|
113
197
|
configuration.load
|
114
198
|
configuration.certificate.should == "/rails_root/config/rapns/production.pem"
|
115
199
|
end
|
116
200
|
|
117
201
|
it "should keep the absolute path of the certificate if it has one" do
|
118
|
-
|
202
|
+
config["certificate"] = "/different_path/to/production.pem"
|
119
203
|
configuration = Rapns::Daemon::Configuration.new("production", "/some/config.yml")
|
120
|
-
configuration.stub(:read_config).and_return({"production" =>
|
204
|
+
configuration.stub(:read_config).and_return({"production" => config})
|
121
205
|
configuration.load
|
122
206
|
configuration.certificate.should == "/different_path/to/production.pem"
|
123
207
|
end
|
124
208
|
|
125
209
|
it "should set the PID file path" do
|
126
210
|
configuration = Rapns::Daemon::Configuration.new("production", "/some/config.yml")
|
127
|
-
configuration.stub(:read_config).and_return({"production" =>
|
211
|
+
configuration.stub(:read_config).and_return({"production" => config})
|
128
212
|
configuration.load
|
129
213
|
configuration.pid_file.should == "/rails_root/rapns.pid"
|
130
214
|
end
|
131
215
|
|
132
216
|
it "should keep the absolute path of the PID file if it has one" do
|
133
|
-
|
217
|
+
config["pid_file"] = "/some/absolue/path/rapns.pid"
|
134
218
|
configuration = Rapns::Daemon::Configuration.new("production", "/some/config.yml")
|
135
|
-
configuration.stub(:read_config).and_return({"production" =>
|
219
|
+
configuration.stub(:read_config).and_return({"production" => config})
|
136
220
|
configuration.load
|
137
221
|
configuration.pid_file.should == "/some/absolue/path/rapns.pid"
|
138
222
|
end
|
139
223
|
|
140
224
|
it "should return nil if no PID file was set" do
|
141
|
-
|
225
|
+
config["pid_file"] = ""
|
142
226
|
configuration = Rapns::Daemon::Configuration.new("production", "/some/config.yml")
|
143
|
-
configuration.stub(:read_config).and_return({"production" =>
|
227
|
+
configuration.stub(:read_config).and_return({"production" => config})
|
144
228
|
configuration.load
|
145
229
|
configuration.pid_file.should be_nil
|
146
230
|
end
|