rapns 3.0.0.beta.1 → 3.0.0.rc.1
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 +75 -106
- data/bin/rapns +14 -14
- data/config/database.yml +8 -0
- data/lib/generators/rapns_generator.rb +5 -1
- data/lib/generators/templates/add_gcm.rb +8 -0
- data/lib/generators/templates/rapns.rb +39 -0
- data/lib/rapns.rb +1 -0
- data/lib/rapns/apns/notification.rb +2 -0
- data/lib/rapns/apns/required_fields_validator.rb +14 -0
- data/lib/rapns/configuration.rb +24 -33
- data/lib/rapns/daemon.rb +18 -14
- data/lib/rapns/daemon/apns/app_runner.rb +1 -1
- data/lib/rapns/daemon/apns/delivery.rb +1 -1
- data/lib/rapns/daemon/apns/feedback_receiver.rb +1 -1
- data/lib/rapns/daemon/app_runner.rb +13 -7
- data/lib/rapns/daemon/delivery_handler.rb +0 -4
- data/lib/rapns/daemon/feeder.rb +1 -5
- data/lib/rapns/daemon/logger.rb +22 -9
- data/lib/rapns/version.rb +1 -1
- data/lib/tasks/cane.rake +2 -3
- data/lib/tasks/test.rake +1 -2
- data/spec/unit/apns/notification_spec.rb +12 -2
- data/spec/unit/configuration_spec.rb +38 -0
- data/spec/unit/daemon/apns/app_runner_spec.rb +2 -0
- data/spec/unit/daemon/apns/delivery_spec.rb +10 -4
- data/spec/unit/daemon/apns/disconnection_error_spec.rb +18 -0
- data/spec/unit/daemon/apns/feedback_receiver_spec.rb +7 -7
- data/spec/unit/daemon/app_runner_spec.rb +51 -0
- data/spec/unit/daemon/database_reconnectable_spec.rb +2 -0
- data/spec/unit/daemon/delivery_error_spec.rb +2 -2
- data/spec/unit/daemon/delivery_handler_shared.rb +10 -1
- data/spec/unit/daemon/gcm/app_runner_spec.rb +3 -1
- data/spec/unit/daemon/logger_spec.rb +10 -2
- data/spec/unit/daemon_spec.rb +31 -14
- data/spec/unit_spec_helper.rb +3 -7
- metadata +10 -4
data/lib/rapns/daemon.rb
CHANGED
@@ -31,15 +31,15 @@ module Rapns
|
|
31
31
|
extend DatabaseReconnectable
|
32
32
|
|
33
33
|
class << self
|
34
|
-
attr_accessor :logger
|
34
|
+
attr_accessor :logger
|
35
35
|
end
|
36
36
|
|
37
|
-
def self.start
|
38
|
-
self.
|
39
|
-
|
37
|
+
def self.start
|
38
|
+
self.logger = Logger.new(:foreground => Rapns.config.foreground,
|
39
|
+
:airbrake_notify => Rapns.config.airbrake_notify)
|
40
40
|
setup_signal_hooks
|
41
41
|
|
42
|
-
unless config.foreground
|
42
|
+
unless Rapns.config.foreground
|
43
43
|
daemonize
|
44
44
|
reconnect_database
|
45
45
|
end
|
@@ -47,7 +47,7 @@ module Rapns
|
|
47
47
|
write_pid_file
|
48
48
|
ensure_upgraded
|
49
49
|
AppRunner.sync
|
50
|
-
Feeder.start(config.push_poll)
|
50
|
+
Feeder.start(Rapns.config.push_poll)
|
51
51
|
end
|
52
52
|
|
53
53
|
protected
|
@@ -61,18 +61,22 @@ module Rapns
|
|
61
61
|
puts "!!!! RAPNS NOT STARTED !!!!"
|
62
62
|
puts
|
63
63
|
puts "As of version v2.0.0 apps are configured in the database instead of rapns.yml."
|
64
|
-
puts "Please run 'rails g rapns' to generate the new migrations and create your
|
64
|
+
puts "Please run 'rails g rapns' to generate the new migrations and create your app."
|
65
65
|
puts "See https://github.com/ileitch/rapns for further instructions."
|
66
66
|
puts
|
67
67
|
exit 1
|
68
68
|
end
|
69
69
|
|
70
70
|
if count == 0
|
71
|
-
logger.warn("You have not created an
|
71
|
+
logger.warn("You have not created an app yet. See https://github.com/ileitch/rapns for instructions.")
|
72
72
|
end
|
73
73
|
|
74
74
|
if File.exists?(File.join(Rails.root, 'config', 'rapns', 'rapns.yml'))
|
75
|
-
logger.warn(
|
75
|
+
logger.warn(<<-EOS)
|
76
|
+
Since 2.0.0 rapns uses command-line options and a Ruby based configuration file.
|
77
|
+
Please run 'rails g rapns' to generate a new configuration file into config/initializers.
|
78
|
+
Remove config/rapns/rapns.yml to avoid this warning.
|
79
|
+
EOS
|
76
80
|
end
|
77
81
|
end
|
78
82
|
|
@@ -80,7 +84,7 @@ module Rapns
|
|
80
84
|
@shutting_down = false
|
81
85
|
|
82
86
|
Signal.trap('SIGHUP') { AppRunner.sync }
|
83
|
-
Signal.trap('
|
87
|
+
Signal.trap('SIGUSR2') { AppRunner.debug }
|
84
88
|
|
85
89
|
['SIGINT', 'SIGTERM'].each do |signal|
|
86
90
|
Signal.trap(signal) { handle_shutdown_signal }
|
@@ -101,17 +105,17 @@ module Rapns
|
|
101
105
|
end
|
102
106
|
|
103
107
|
def self.write_pid_file
|
104
|
-
if !config.pid_file.blank?
|
108
|
+
if !Rapns.config.pid_file.blank?
|
105
109
|
begin
|
106
|
-
File.open(config.pid_file, 'w') { |f| f.puts Process.pid }
|
110
|
+
File.open(Rapns.config.pid_file, 'w') { |f| f.puts Process.pid }
|
107
111
|
rescue SystemCallError => e
|
108
|
-
logger.error("Failed to write PID to '#{config.pid_file}': #{e.inspect}")
|
112
|
+
logger.error("Failed to write PID to '#{Rapns.config.pid_file}': #{e.inspect}")
|
109
113
|
end
|
110
114
|
end
|
111
115
|
end
|
112
116
|
|
113
117
|
def self.delete_pid_file
|
114
|
-
pid_file = config.pid_file
|
118
|
+
pid_file = Rapns.config.pid_file
|
115
119
|
File.delete(pid_file) if !pid_file.blank? && File.exists?(pid_file)
|
116
120
|
end
|
117
121
|
|
@@ -16,7 +16,7 @@ module Rapns
|
|
16
16
|
protected
|
17
17
|
|
18
18
|
def started
|
19
|
-
poll = Rapns
|
19
|
+
poll = Rapns.config[:feedback_poll]
|
20
20
|
host, port = ENVIRONMENTS[app.environment.to_sym][:feedback]
|
21
21
|
@feedback_receiver = FeedbackReceiver.new(app, host, port, poll)
|
22
22
|
@feedback_receiver.start
|
@@ -25,7 +25,7 @@ module Rapns
|
|
25
25
|
def perform
|
26
26
|
begin
|
27
27
|
@connection.write(@notification.to_binary)
|
28
|
-
check_for_error if Rapns
|
28
|
+
check_for_error if Rapns.config.check_for_errors
|
29
29
|
mark_delivered
|
30
30
|
Rapns::Daemon.logger.info("[#{@app.name}] #{@notification.id} sent to #{@notification.device_token}")
|
31
31
|
rescue Rapns::DeliveryError, Rapns::Apns::DisconnectionError => error
|
@@ -62,7 +62,7 @@ module Rapns
|
|
62
62
|
Rapns::Daemon.logger.info("[FeedbackReceiver:#{@app.name}] Delivery failed at #{formatted_failed_at} for #{device_token}")
|
63
63
|
feedback = Rapns::Apns::Feedback.create!(:failed_at => failed_at, :device_token => device_token, :app => @app)
|
64
64
|
begin
|
65
|
-
Rapns.
|
65
|
+
Rapns.config.apns_feedback_callback.call(feedback) if Rapns.config.apns_feedback_callback
|
66
66
|
rescue StandardError => e
|
67
67
|
Rapns::Daemon.logger.error(e)
|
68
68
|
end
|
@@ -8,7 +8,7 @@ module Rapns
|
|
8
8
|
@runners = {}
|
9
9
|
|
10
10
|
def self.enqueue(notification)
|
11
|
-
if app =
|
11
|
+
if app = runners[notification.app_id]
|
12
12
|
app.enqueue(notification)
|
13
13
|
else
|
14
14
|
Rapns::Daemon.logger.error("No such app '#{notification.app_id}' for notification #{notification.id}.")
|
@@ -43,11 +43,11 @@ module Rapns
|
|
43
43
|
end
|
44
44
|
|
45
45
|
def self.stop
|
46
|
-
|
46
|
+
runners.values.map(&:stop)
|
47
47
|
end
|
48
48
|
|
49
49
|
def self.debug
|
50
|
-
|
50
|
+
runners.values.map(&:debug)
|
51
51
|
end
|
52
52
|
|
53
53
|
def self.idle
|
@@ -60,10 +60,6 @@ module Rapns
|
|
60
60
|
@app = app
|
61
61
|
end
|
62
62
|
|
63
|
-
def new_delivery_handler
|
64
|
-
raise NotImplementedError
|
65
|
-
end
|
66
|
-
|
67
63
|
def started
|
68
64
|
end
|
69
65
|
|
@@ -73,6 +69,7 @@ module Rapns
|
|
73
69
|
def start
|
74
70
|
app.connections.times { handlers << start_handler }
|
75
71
|
started
|
72
|
+
Rapns::Daemon.logger.info("[#{app.name}] Started, #{handlers_str}.")
|
76
73
|
end
|
77
74
|
|
78
75
|
def stop
|
@@ -87,10 +84,13 @@ module Rapns
|
|
87
84
|
def sync(app)
|
88
85
|
@app = app
|
89
86
|
diff = handlers.size - app.connections
|
87
|
+
return if diff == 0
|
90
88
|
if diff > 0
|
91
89
|
diff.times { handlers.pop.stop }
|
90
|
+
Rapns::Daemon.logger.info("[#{app.name}] Terminated #{handlers_str(diff)}. #{handlers_str} remaining.")
|
92
91
|
else
|
93
92
|
diff.abs.times { handlers << start_handler }
|
93
|
+
Rapns::Daemon.logger.info("[#{app.name}] Added #{handlers_str(diff)}. #{handlers_str} remaining.")
|
94
94
|
end
|
95
95
|
end
|
96
96
|
|
@@ -124,6 +124,12 @@ module Rapns
|
|
124
124
|
def handlers
|
125
125
|
@handler ||= []
|
126
126
|
end
|
127
|
+
|
128
|
+
def handlers_str(count = app.connections)
|
129
|
+
count = count.abs
|
130
|
+
str = count == 1 ? 'handler' : 'handlers'
|
131
|
+
"#{count} #{str}"
|
132
|
+
end
|
127
133
|
end
|
128
134
|
end
|
129
135
|
end
|
data/lib/rapns/daemon/feeder.rb
CHANGED
@@ -4,10 +4,6 @@ module Rapns
|
|
4
4
|
extend InterruptibleSleep
|
5
5
|
extend DatabaseReconnectable
|
6
6
|
|
7
|
-
def self.name
|
8
|
-
'Feeder'
|
9
|
-
end
|
10
|
-
|
11
7
|
def self.start(poll)
|
12
8
|
loop do
|
13
9
|
enqueue_notifications
|
@@ -26,7 +22,7 @@ module Rapns
|
|
26
22
|
def self.enqueue_notifications
|
27
23
|
begin
|
28
24
|
with_database_reconnect_and_retry do
|
29
|
-
batch_size = Rapns
|
25
|
+
batch_size = Rapns.config.batch_size
|
30
26
|
idle = Rapns::Daemon::AppRunner.idle.map(&:app)
|
31
27
|
Rapns::Notification.ready_for_delivery.for_apps(idle).limit(batch_size).each do |notification|
|
32
28
|
Rapns::Daemon::AppRunner.enqueue(notification)
|
data/lib/rapns/daemon/logger.rb
CHANGED
@@ -3,10 +3,17 @@ module Rapns
|
|
3
3
|
class Logger
|
4
4
|
def initialize(options)
|
5
5
|
@options = options
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
|
7
|
+
begin
|
8
|
+
log = File.open(File.join(Rails.root, 'log', 'rapns.log'), 'a')
|
9
|
+
log.sync = true
|
10
|
+
@logger = ActiveSupport::BufferedLogger.new(log, Rails.logger.level)
|
11
|
+
@logger.auto_flushing = Rails.logger.respond_to?(:auto_flushing) ? Rails.logger.auto_flushing : true
|
12
|
+
rescue Errno::ENOENT, Errno::EPERM => e
|
13
|
+
@logger = nil
|
14
|
+
error(e)
|
15
|
+
error('Logging disabled.')
|
16
|
+
end
|
10
17
|
end
|
11
18
|
|
12
19
|
def info(msg)
|
@@ -15,16 +22,16 @@ module Rapns
|
|
15
22
|
|
16
23
|
def error(msg, options = {})
|
17
24
|
airbrake_notify(msg) if notify_via_airbrake?(msg, options)
|
18
|
-
log(:error, msg, 'ERROR')
|
25
|
+
log(:error, msg, 'ERROR', STDERR)
|
19
26
|
end
|
20
27
|
|
21
28
|
def warn(msg)
|
22
|
-
log(:warn, msg, 'WARNING')
|
29
|
+
log(:warn, msg, 'WARNING', STDERR)
|
23
30
|
end
|
24
31
|
|
25
32
|
private
|
26
33
|
|
27
|
-
def log(where, msg, prefix = nil)
|
34
|
+
def log(where, msg, prefix = nil, io = STDOUT)
|
28
35
|
if msg.is_a?(Exception)
|
29
36
|
formatted_backtrace = msg.backtrace.join("\n")
|
30
37
|
msg = "#{msg.class.name}, #{msg.message}\n#{formatted_backtrace}"
|
@@ -33,8 +40,14 @@ module Rapns
|
|
33
40
|
formatted_msg = "[#{Time.now.to_s(:db)}] "
|
34
41
|
formatted_msg << "[#{prefix}] " if prefix
|
35
42
|
formatted_msg << msg
|
36
|
-
|
37
|
-
|
43
|
+
|
44
|
+
if io == STDERR
|
45
|
+
io.puts formatted_msg
|
46
|
+
elsif @options[:foreground]
|
47
|
+
io.puts formatted_msg
|
48
|
+
end
|
49
|
+
|
50
|
+
@logger.send(where, formatted_msg) if @logger
|
38
51
|
end
|
39
52
|
|
40
53
|
def airbrake_notify(e)
|
data/lib/rapns/version.rb
CHANGED
data/lib/tasks/cane.rake
CHANGED
@@ -3,12 +3,11 @@ begin
|
|
3
3
|
|
4
4
|
desc "Run cane to check quality metrics"
|
5
5
|
Cane::RakeTask.new(:quality) do |cane|
|
6
|
-
cane.add_threshold 'coverage/covered_percent', :>=,
|
6
|
+
cane.add_threshold 'coverage/covered_percent', :>=, 99
|
7
7
|
cane.no_style = false
|
8
8
|
cane.style_measure = 1000
|
9
9
|
cane.no_doc = true
|
10
|
-
cane.abc_max =
|
11
|
-
cane.abc_exclude = %w(Rapns::Daemon::Gcm::Delivery#handle_errors)
|
10
|
+
cane.abc_max = 20
|
12
11
|
end
|
13
12
|
|
14
13
|
namespace :spec do
|
data/lib/tasks/test.rake
CHANGED
@@ -16,12 +16,11 @@ namespace :test do
|
|
16
16
|
pwd = Dir.pwd
|
17
17
|
|
18
18
|
cmd("bundle exec rails new #{path} --skip-bundle")
|
19
|
-
branch = cmd("git branch | grep '\*'").split(' ').last
|
20
19
|
|
21
20
|
begin
|
22
21
|
Dir.chdir(path)
|
23
22
|
cmd('echo "gem \'rake\'" >> Gemfile')
|
24
|
-
cmd("echo \"gem 'rapns', :
|
23
|
+
cmd("echo \"gem 'rapns', :path => '#{rapns_root}'\" >> Gemfile")
|
25
24
|
cmd('bundle install')
|
26
25
|
cmd('bundle exec rails g rapns')
|
27
26
|
cmd('bundle exec rake db:migrate')
|
@@ -25,13 +25,23 @@ describe Rapns::Apns::Notification do
|
|
25
25
|
notification.errors[:base].include?("APN notification cannot be larger than 256 bytes. Try condensing your alert and device attributes.").should be_true
|
26
26
|
end
|
27
27
|
|
28
|
-
it "should default the sound to
|
29
|
-
notification.sound.should
|
28
|
+
it "should default the sound to 'default'" do
|
29
|
+
notification.sound.should eq('default')
|
30
30
|
end
|
31
31
|
|
32
32
|
it "should default the expiry to 1 day" do
|
33
33
|
notification.expiry.should == 1.day.to_i
|
34
34
|
end
|
35
|
+
|
36
|
+
# The notification must contain one of alert, sound or badge.
|
37
|
+
# @see https://developer.apple.com/library/ios/#documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/ApplePushService/ApplePushService.html
|
38
|
+
it "should not be valid if there is none of alert,sound,badge present" do
|
39
|
+
notification.alert = nil
|
40
|
+
notification.sound = nil
|
41
|
+
notification.badge = nil
|
42
|
+
notification.valid?.should be_false
|
43
|
+
notification.errors[:base].should include("APN Notification must contain one of alert, badge, or sound")
|
44
|
+
end
|
35
45
|
end
|
36
46
|
|
37
47
|
describe Rapns::Apns::Notification, "when assigning the device token" do
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'unit_spec_helper'
|
2
|
+
|
3
|
+
describe Rapns do
|
4
|
+
let(:config) { stub }
|
5
|
+
|
6
|
+
before { Rapns.stub(:config => config) }
|
7
|
+
|
8
|
+
it 'can yields a config block' do
|
9
|
+
expect { |b| Rapns.configure(&b) }.to yield_with_args(config)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe Rapns::Configuration do
|
14
|
+
let(:config) { Rapns::Configuration.new }
|
15
|
+
|
16
|
+
it 'configures a feedback callback' do
|
17
|
+
b = Proc.new {}
|
18
|
+
config.on_apns_feedback(&b)
|
19
|
+
config.apns_feedback_callback.should == b
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'can be updated' do
|
23
|
+
new_config = Rapns::Configuration.new
|
24
|
+
new_config.batch_size = 100
|
25
|
+
expect { config.update(new_config) }.to change(config, :batch_size).to(100)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'sets the pid_file relative if not absolute' do
|
29
|
+
Rails.stub(:root => '/rails')
|
30
|
+
config.pid_file = 'tmp/rapns.pid'
|
31
|
+
config.pid_file.should == '/rails/tmp/rapns.pid'
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'does not alter an absolute pid_file path' do
|
35
|
+
config.pid_file = '/tmp/rapns.pid'
|
36
|
+
config.pid_file.should == '/tmp/rapns.pid'
|
37
|
+
end
|
38
|
+
end
|
@@ -11,8 +11,10 @@ describe Rapns::Daemon::Apns::AppRunner do
|
|
11
11
|
let(:handler) { stub(:start => nil, :stop => nil, :queue= => nil) }
|
12
12
|
let(:receiver) { stub(:start => nil, :stop => nil) }
|
13
13
|
let(:config) { {:feedback_poll => 60 } }
|
14
|
+
let(:logger) { stub(:info => nil) }
|
14
15
|
|
15
16
|
before do
|
17
|
+
Rapns::Daemon.stub(:logger => logger)
|
16
18
|
Rapns::Daemon::Apns::DeliveryHandler.stub(:new => handler)
|
17
19
|
Rapns::Daemon::Apns::FeedbackReceiver.stub(:new => receiver)
|
18
20
|
Rapns::Daemon.stub(:config => config)
|
@@ -16,7 +16,8 @@ describe Rapns::Daemon::Apns::Delivery do
|
|
16
16
|
end
|
17
17
|
|
18
18
|
before do
|
19
|
-
Rapns
|
19
|
+
Rapns.stub(:config => config)
|
20
|
+
Rapns::Daemon.stub(:logger => logger)
|
20
21
|
end
|
21
22
|
|
22
23
|
it "sends the binary version of the notification" do
|
@@ -95,9 +96,14 @@ describe Rapns::Daemon::Apns::Delivery do
|
|
95
96
|
end
|
96
97
|
|
97
98
|
it "logs the delivery error" do
|
98
|
-
error
|
99
|
-
|
100
|
-
|
99
|
+
# checking for the stubbed error doesn't work in jruby, but checking
|
100
|
+
# for the exception by class does.
|
101
|
+
|
102
|
+
#error = Rapns::DeliveryError.new(4, 12, "Missing payload")
|
103
|
+
#Rapns::DeliveryError.stub(:new => error)
|
104
|
+
#expect { delivery.perform }.to raise_error(error)
|
105
|
+
|
106
|
+
expect { delivery.perform }.to raise_error(Rapns::DeliveryError)
|
101
107
|
end
|
102
108
|
|
103
109
|
it "sets the notification error description" do
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'unit_spec_helper'
|
2
|
+
|
3
|
+
describe Rapns::Apns::DisconnectionError do
|
4
|
+
let(:error) { Rapns::Apns::DisconnectionError.new }
|
5
|
+
|
6
|
+
it 'returns a nil error code' do
|
7
|
+
error.code.should be_nil
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'contains an error description' do
|
11
|
+
error.description
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'returns a message' do
|
15
|
+
error.message
|
16
|
+
error.to_s
|
17
|
+
end
|
18
|
+
end
|
@@ -90,29 +90,29 @@ describe Rapns::Daemon::Apns::FeedbackReceiver, 'check_for_feedback' do
|
|
90
90
|
receiver.stop
|
91
91
|
end
|
92
92
|
|
93
|
-
it 'calls the
|
93
|
+
it 'calls the apns_feedback_callback when feedback is received and the callback is set' do
|
94
94
|
stub_connection_read_with_tuple
|
95
|
-
Rapns.
|
95
|
+
Rapns.config.apns_feedback_callback = Proc.new {}
|
96
96
|
feedback = Object.new
|
97
97
|
Rapns::Apns::Feedback.stub(:create! => feedback)
|
98
|
-
Rapns.
|
98
|
+
Rapns.config.apns_feedback_callback.should_receive(:call).with(feedback)
|
99
99
|
receiver.check_for_feedback
|
100
100
|
end
|
101
101
|
|
102
|
-
it 'catches exceptions in the
|
102
|
+
it 'catches exceptions in the apns_feedback_callback' do
|
103
103
|
error = StandardError.new('bork!')
|
104
104
|
stub_connection_read_with_tuple
|
105
105
|
callback = Proc.new { raise error }
|
106
|
-
Rapns
|
106
|
+
Rapns.config.on_apns_feedback &callback
|
107
107
|
expect { receiver.check_for_feedback }.not_to raise_error
|
108
108
|
end
|
109
109
|
|
110
|
-
it 'logs an exception from the
|
110
|
+
it 'logs an exception from the apns_feedback_callback' do
|
111
111
|
error = StandardError.new('bork!')
|
112
112
|
stub_connection_read_with_tuple
|
113
113
|
callback = Proc.new { raise error }
|
114
114
|
Rapns::Daemon.logger.should_receive(:error).with(error)
|
115
|
-
Rapns
|
115
|
+
Rapns.config.on_apns_feedback &callback
|
116
116
|
receiver.check_for_feedback
|
117
117
|
end
|
118
118
|
end
|