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