rpush 2.2.0 → 2.3.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -0
  3. data/README.md +17 -9
  4. data/lib/generators/rpush_migration_generator.rb +1 -1
  5. data/lib/rpush.rb +4 -1
  6. data/lib/rpush/apns_feedback.rb +1 -1
  7. data/lib/rpush/cli.rb +25 -22
  8. data/lib/rpush/client/active_model/apns/app.rb +1 -1
  9. data/lib/rpush/client/active_model/apns/notification.rb +2 -2
  10. data/lib/rpush/client/active_model/wpns/notification.rb +7 -1
  11. data/lib/rpush/configuration.rb +32 -22
  12. data/lib/rpush/daemon.rb +18 -9
  13. data/lib/rpush/daemon/adm/delivery.rb +4 -1
  14. data/lib/rpush/daemon/apns/delivery.rb +3 -0
  15. data/lib/rpush/daemon/apns/feedback_receiver.rb +6 -2
  16. data/lib/rpush/daemon/app_runner.rb +11 -16
  17. data/lib/rpush/daemon/batch.rb +9 -0
  18. data/lib/rpush/daemon/delivery.rb +10 -2
  19. data/lib/rpush/daemon/delivery_error.rb +3 -3
  20. data/lib/rpush/daemon/dispatcher/apns_tcp.rb +11 -11
  21. data/lib/rpush/daemon/dispatcher/tcp.rb +2 -10
  22. data/lib/rpush/daemon/gcm/delivery.rb +13 -7
  23. data/lib/rpush/daemon/signal_handler.rb +5 -3
  24. data/lib/rpush/daemon/store/active_record.rb +14 -0
  25. data/lib/rpush/daemon/store/interface.rb +2 -1
  26. data/lib/rpush/daemon/store/redis.rb +3 -0
  27. data/lib/rpush/daemon/tcp_connection.rb +47 -17
  28. data/lib/rpush/daemon/wpns/delivery.rb +19 -10
  29. data/lib/rpush/logger.rb +30 -12
  30. data/lib/rpush/plugin.rb +44 -0
  31. data/lib/rpush/push.rb +1 -1
  32. data/lib/rpush/reflectable.rb +11 -0
  33. data/lib/rpush/{reflection.rb → reflection_collection.rb} +1 -9
  34. data/lib/rpush/reflection_public_methods.rb +9 -0
  35. data/lib/rpush/version.rb +1 -1
  36. data/lib/tasks/test.rake +6 -4
  37. data/spec/functional/adm_spec.rb +12 -0
  38. data/spec/functional/apns_spec.rb +61 -42
  39. data/spec/functional/gcm_spec.rb +9 -0
  40. data/spec/functional/retry_spec.rb +1 -1
  41. data/spec/functional/wpns_spec.rb +44 -11
  42. data/spec/spec_helper.rb +2 -0
  43. data/spec/unit/apns_feedback_spec.rb +2 -2
  44. data/spec/unit/client/active_record/apns/app_spec.rb +2 -2
  45. data/spec/unit/client/active_record/apns/notification_spec.rb +1 -2
  46. data/spec/unit/client/active_record/wpns/notification_spec.rb +9 -3
  47. data/spec/unit/configuration_spec.rb +1 -0
  48. data/spec/unit/daemon/adm/delivery_spec.rb +0 -1
  49. data/spec/unit/daemon/apns/feedback_receiver_spec.rb +0 -1
  50. data/spec/unit/daemon/app_runner_spec.rb +14 -9
  51. data/spec/unit/daemon/delivery_spec.rb +0 -1
  52. data/spec/unit/daemon/dispatcher/tcp_spec.rb +0 -7
  53. data/spec/unit/daemon/gcm/delivery_spec.rb +1 -1
  54. data/spec/unit/daemon/signal_handler_spec.rb +5 -1
  55. data/spec/unit/daemon/store/active_record_spec.rb +1 -1
  56. data/spec/unit/daemon/tcp_connection_spec.rb +18 -18
  57. data/spec/unit/daemon/wpns/delivery_spec.rb +1 -1
  58. data/spec/unit/daemon_spec.rb +10 -2
  59. data/spec/unit/logger_spec.rb +0 -1
  60. data/spec/unit/plugin_spec.rb +36 -0
  61. data/spec/unit/push_spec.rb +2 -2
  62. data/spec/unit/{daemon/reflectable_spec.rb → reflectable_spec.rb} +6 -6
  63. data/spec/unit/{reflection_spec.rb → reflection_collection_spec.rb} +4 -8
  64. metadata +16 -18
  65. data/lib/rpush/daemon/reflectable.rb +0 -11
  66. data/spec/integration/rpush_spec.rb +0 -13
  67. data/spec/integration/support/gcm_success_response.json +0 -1
  68. data/spec/support/install.sh +0 -68
@@ -17,6 +17,9 @@ module Rpush
17
17
 
18
18
  def perform
19
19
  handle_response(do_post)
20
+ rescue SocketError => error
21
+ mark_retryable(@notification, Time.now + 10.seconds, error)
22
+ raise
20
23
  rescue StandardError => error
21
24
  mark_failed(error)
22
25
  raise
@@ -77,7 +80,7 @@ module Rpush
77
80
  end
78
81
 
79
82
  def retry_message
80
- "Notification #{@notification.id} will be retried after #{@notification.deliver_after.strftime("%Y-%m-%d %H:%M:%S")} (retry #{@notification.retries})."
83
+ "Notification #{@notification.id} will be retried after #{@notification.deliver_after.strftime('%Y-%m-%d %H:%M:%S')} (retry #{@notification.retries})."
81
84
  end
82
85
 
83
86
  def retry_notification(reason)
@@ -106,16 +109,22 @@ module Rpush
106
109
  end
107
110
 
108
111
  def notification_to_xml
109
- msg = @notification.alert.gsub(/&/, "&amp;").gsub(/</, "&lt;") \
112
+ title = clean_param_string(@notification.data['title']) if @notification.data['title'].present?
113
+ body = clean_param_string(@notification.data['body']) if @notification.data['body'].present?
114
+ param = clean_param_string(@notification.data['param']) if @notification.data['param'].present?
115
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>
116
+ <wp:Notification xmlns:wp=\"WPNotification\">
117
+ <wp:Toast>
118
+ <wp:Text1>#{title}</wp:Text1>
119
+ <wp:Text2>#{body}</wp:Text2>
120
+ <wp:Param>#{param}</wp:Param>
121
+ </wp:Toast>
122
+ </wp:Notification>"
123
+ end
124
+
125
+ def clean_param_string(string)
126
+ string.gsub(/&/, "&amp;").gsub(/</, "&lt;") \
110
127
  .gsub(/>/, "&gt;").gsub(/'/, "&apos;").gsub(/"/, "&quot;")
111
- <<-EOF
112
- <?xml version="1.0" encoding="utf-8"?>
113
- <wp:Notification xmlns:wp="WPNotification">
114
- <wp:Toast>
115
- <wp:Text1>#{msg}</wp:Text1>
116
- </wp:Toast>
117
- </wp:Notification>
118
- EOF
119
128
  end
120
129
  end
121
130
  end
@@ -1,12 +1,15 @@
1
1
  module Rpush
2
2
  class Logger
3
+ attr_reader :internal_logger
4
+
3
5
  def initialize
4
- FileUtils.mkdir_p(File.dirname(Rpush.config.log_file))
5
- log = File.open(Rpush.config.log_file, 'a')
6
- log.sync = true
7
- setup_logger(log)
6
+ if Rpush.config.logger
7
+ @internal_logger = Rpush.config.logger
8
+ else
9
+ @internal_logger = setup_logger(open_logfile)
10
+ end
8
11
  rescue Errno::ENOENT, Errno::EPERM => e
9
- @logger = nil
12
+ @internal_logger = nil
10
13
  error(e)
11
14
  error('Logging disabled.')
12
15
  end
@@ -23,16 +26,31 @@ module Rpush
23
26
  log(:warn, msg, inline, 'WARNING', STDERR)
24
27
  end
25
28
 
29
+ def reopen
30
+ if Rpush.config.logger
31
+ Rpush.config.logger.reopen if Rpush.config.logger.respond_to?(:reopen)
32
+ else
33
+ @internal_logger.close
34
+ @internal_logger = setup_logger(open_logfile)
35
+ end
36
+ end
37
+
26
38
  private
27
39
 
40
+ def open_logfile
41
+ FileUtils.mkdir_p(File.dirname(Rpush.config.log_file))
42
+ log = File.open(Rpush.config.log_file, 'a')
43
+ log.sync = true
44
+ log
45
+ end
46
+
28
47
  def setup_logger(log)
29
- if Rpush.config.logger
30
- @logger = Rpush.config.logger
31
- elsif ActiveSupport.const_defined?('BufferedLogger')
32
- @logger = ActiveSupport::BufferedLogger.new(log, Rpush.config.log_level)
33
- @logger.auto_flushing = auto_flushing
48
+ if ActiveSupport.const_defined?('BufferedLogger')
49
+ logger = ActiveSupport::BufferedLogger.new(log, Rpush.config.log_level)
50
+ logger.auto_flushing = auto_flushing
51
+ logger
34
52
  else
35
- @logger = ActiveSupport::Logger.new(log, Rpush.config.log_level)
53
+ ActiveSupport::Logger.new(log, Rpush.config.log_level)
36
54
  end
37
55
  end
38
56
 
@@ -55,7 +73,7 @@ module Rpush
55
73
  formatted_msg << msg
56
74
 
57
75
  log_foreground(io, formatted_msg, inline)
58
- @logger.send(where, formatted_msg) if @logger
76
+ @internal_logger.send(where, formatted_msg) if @internal_logger
59
77
  end
60
78
 
61
79
  def log_foreground(io, formatted_msg, inline)
@@ -0,0 +1,44 @@
1
+ module Rpush
2
+ def self.plugin(name)
3
+ plugins[name] ||= Rpush::Plugin.new(name)
4
+ plugins[name]
5
+ end
6
+
7
+ def self.plugins
8
+ @plugins ||= {}
9
+ end
10
+
11
+ class Plugin
12
+ attr_reader :name, :config, :init_block
13
+ attr_accessor :url, :description
14
+
15
+ def initialize(name)
16
+ @name = name
17
+ @url = nil
18
+ @description = nil
19
+ @config = OpenStruct.new
20
+ @reflection_collection = Rpush::ReflectionCollection.new
21
+ @init_block = -> {}
22
+ end
23
+
24
+ def reflect
25
+ yield(@reflection_collection)
26
+ return if Rpush.reflection_stack.include?(@reflection_collection)
27
+ Rpush.reflection_stack << @reflection_collection
28
+ end
29
+
30
+ def configure
31
+ yield(@config)
32
+ Rpush.config.plugin.send("#{@name}=", @config)
33
+ end
34
+
35
+ def init(&block) # rubocop:disable Style/TrivialAccessors
36
+ @init_block = block
37
+ end
38
+
39
+ def unload
40
+ Rpush.reflection_stack.delete(@reflection_collection)
41
+ Rpush.config.plugin.send("#{name}=", nil)
42
+ end
43
+ end
44
+ end
@@ -7,7 +7,7 @@ module Rpush
7
7
  config.push = true
8
8
  Rpush.config.update(config)
9
9
 
10
- Rpush::Daemon.initialize_store
10
+ Rpush::Daemon.common_init
11
11
  Rpush::Daemon::Synchronizer.sync
12
12
  Rpush::Daemon::Feeder.start
13
13
  # TODO: Wait until there are no more notifications.
@@ -0,0 +1,11 @@
1
+ module Rpush
2
+ module Reflectable
3
+ def reflect(name, *args)
4
+ Rpush.reflection_stack.each do |reflection_collection|
5
+ reflection_collection.__dispatch(name, *args)
6
+ end
7
+ rescue StandardError => e
8
+ Rpush.logger.error(e)
9
+ end
10
+ end
11
+ end
@@ -1,13 +1,5 @@
1
1
  module Rpush
2
- def self.reflect
3
- yield reflections if block_given?
4
- end
5
-
6
- def self.reflections
7
- @reflections ||= Reflections.new
8
- end
9
-
10
- class Reflections
2
+ class ReflectionCollection
11
3
  class NoSuchReflectionError < StandardError; end
12
4
 
13
5
  REFLECTIONS = [
@@ -0,0 +1,9 @@
1
+ module Rpush
2
+ def self.reflect
3
+ yield reflection_stack[0] if block_given?
4
+ end
5
+
6
+ def self.reflection_stack
7
+ @reflection_stack ||= [ReflectionCollection.new]
8
+ end
9
+ end
@@ -1,3 +1,3 @@
1
1
  module Rpush
2
- VERSION = '2.2.0'
2
+ VERSION = '2.3.0.rc1'
3
3
  end
@@ -7,6 +7,7 @@ def cmd(str, clean_env = true)
7
7
  retval
8
8
  end
9
9
 
10
+ desc 'Build Rails app bundled with Rpush'
10
11
  task :build_rails do
11
12
  rpush_root = Dir.pwd
12
13
  path = '/tmp/rpush/rails_test'
@@ -27,10 +28,10 @@ task :build_rails do
27
28
  File.open('config/database.yml', 'w') do |fd|
28
29
  fd.write(<<-YML)
29
30
  development:
30
- adapter: postgresql
31
- database: rpush_rails_test
32
- pool: 5
33
- timeout: 5000
31
+ adapter: postgresql
32
+ database: rpush_rails_test
33
+ pool: 5
34
+ timeout: 5000
34
35
  YML
35
36
  end
36
37
  ensure
@@ -40,6 +41,7 @@ timeout: 5000
40
41
  puts "Built into #{path}"
41
42
  end
42
43
 
44
+ desc 'Build blank app bundled with Rpush'
43
45
  task :build_standalone do
44
46
  rpush_root = Dir.pwd
45
47
  path = '/tmp/rpush/standalone_test'
@@ -5,6 +5,9 @@ describe 'ADM' do
5
5
  let(:notification) { Rpush::Adm::Notification.new }
6
6
  let(:response) { double(Net::HTTPResponse, code: 200) }
7
7
  let(:http) { double(Net::HTTP::Persistent, request: response, shutdown: nil) }
8
+ let(:delivered_ids) { [] }
9
+ let(:failed_ids) { [] }
10
+ let(:retry_ids) { [] }
8
11
 
9
12
  before do
10
13
  app.name = 'test'
@@ -37,4 +40,13 @@ describe 'ADM' do
37
40
  notification.reload
38
41
  end.to_not change(notification, :delivered).to(true)
39
42
  end
43
+
44
+ it 'retries notification that fail due to a SocketError' do
45
+ expect(http).to receive(:request).and_raise(SocketError.new)
46
+ expect(notification.deliver_after).to be_nil
47
+ expect do
48
+ Rpush.push
49
+ notification.reload
50
+ end.to change(notification, :deliver_after).to(kind_of(Time))
51
+ end
40
52
  end
@@ -1,14 +1,16 @@
1
1
  require 'functional_spec_helper'
2
2
 
3
3
  describe 'APNs' do
4
- let(:timeout) { 10 }
5
4
  let(:app) { create_app }
6
- let!(:notification) { create_notification }
7
5
  let(:tcp_socket) { double(TCPSocket, setsockopt: nil, close: nil) }
8
6
  let(:ssl_socket) { double(OpenSSL::SSL::SSLSocket, :sync= => nil, connect: nil, write: nil, flush: nil, read: nil, close: nil) }
9
7
  let(:io_double) { double(select: nil) }
8
+ let(:delivered_ids) { [] }
9
+ let(:failed_ids) { [] }
10
+ let(:retry_ids) { [] }
10
11
 
11
12
  before do
13
+ Rpush.config.push_poll = 0.5
12
14
  stub_tcp_connection
13
15
  end
14
16
 
@@ -36,31 +38,20 @@ describe 'APNs' do
36
38
  stub_const('Rpush::Daemon::TcpConnection::IO', io_double)
37
39
  end
38
40
 
41
+ def wait
42
+ sleep 0.1
43
+ end
44
+
39
45
  def wait_for_notification_to_deliver(notification)
40
- Timeout.timeout(timeout) do
41
- until notification.delivered
42
- sleep 0.1
43
- notification.reload
44
- end
45
- end
46
+ timeout { wait until delivered_ids.include?(notification.id) }
46
47
  end
47
48
 
48
49
  def wait_for_notification_to_fail(notification)
49
- Timeout.timeout(timeout) do
50
- while notification.delivered
51
- sleep 0.1
52
- notification.reload
53
- end
54
- end
50
+ timeout { wait until failed_ids.include?(notification.id) }
55
51
  end
56
52
 
57
53
  def wait_for_notification_to_retry(notification)
58
- Timeout.timeout(timeout) do
59
- until !notification.delivered && !notification.failed && !notification.deliver_after.nil?
60
- sleep 0.1
61
- notification.reload
62
- end
63
- end
54
+ timeout { wait until retry_ids.include?(notification.id) }
64
55
  end
65
56
 
66
57
  def fail_notification(notification)
@@ -79,7 +70,12 @@ describe 'APNs' do
79
70
  end
80
71
  end
81
72
 
73
+ def timeout(&blk)
74
+ Timeout.timeout(10, &blk)
75
+ end
76
+
82
77
  it 'delivers a notification successfully' do
78
+ notification = create_notification
83
79
  expect do
84
80
  Rpush.push
85
81
  notification.reload
@@ -87,6 +83,7 @@ describe 'APNs' do
87
83
  end
88
84
 
89
85
  it 'receives feedback' do
86
+ app
90
87
  tuple = "N\xE3\x84\r\x00 \x83OxfU\xEB\x9F\x84aJ\x05\xAD}\x00\xAF1\xE5\xCF\xE9:\xC3\xEA\a\x8F\x1D\xA4M*N\xB0\xCE\x17"
91
88
  allow(ssl_socket).to receive(:read).and_return(tuple, nil)
92
89
  Rpush.apns_feedback
@@ -97,21 +94,57 @@ describe 'APNs' do
97
94
  end
98
95
 
99
96
  describe 'delivery failures' do
100
- before { Rpush.embed }
101
- after { Timeout.timeout(timeout) { Rpush.shutdown } }
97
+ before do
98
+ Rpush.reflect do |on|
99
+ on.notification_delivered do |n|
100
+ delivered_ids << n.id
101
+ end
102
+
103
+ on.notification_id_failed do |_, n_id|
104
+ failed_ids << n_id
105
+ end
106
+
107
+ on.notification_id_will_retry do |_, n_id|
108
+ retry_ids << n_id
109
+ end
110
+
111
+ on.notification_will_retry do |n|
112
+ retry_ids << n.id
113
+ end
114
+ end
115
+
116
+ Rpush.embed
117
+ end
118
+
119
+ after do
120
+ Rpush.reflection_stack.clear
121
+ Rpush.reflection_stack.push(Rpush::ReflectionCollection.new)
122
+
123
+ timeout { Rpush.shutdown }
124
+ end
102
125
 
103
126
  it 'fails to deliver a notification' do
127
+ notification = create_notification
104
128
  wait_for_notification_to_deliver(notification)
105
129
  fail_notification(notification)
106
130
  wait_for_notification_to_fail(notification)
107
131
  end
108
132
 
133
+ describe 'with a failed connection' do
134
+ it 'retries all notifications' do
135
+ Rpush::Daemon::TcpConnection.any_instance.stub(sleep: nil)
136
+ expect(ssl_socket).to receive(:write).at_least(1).times.and_raise(Errno::EPIPE)
137
+ notifications = 2.times.map { create_notification }
138
+ notifications.each { |n| wait_for_notification_to_retry(n) }
139
+ end
140
+ end
141
+
109
142
  describe 'with multiple notifications' do
110
143
  let(:notification1) { create_notification }
111
144
  let(:notification2) { create_notification }
112
145
  let(:notification3) { create_notification }
113
146
  let(:notification4) { create_notification }
114
- let!(:notifications) { [notification1, notification2, notification3, notification4] }
147
+ let(:notifications) { [notification1, notification2, notification3, notification4] }
115
148
 
116
149
  it 'marks the correct notification as failed' do
117
150
  notifications.each { |n| wait_for_notification_to_deliver(n) }
@@ -124,29 +157,15 @@ describe 'APNs' do
124
157
  fail_notification(notification2)
125
158
  wait_for_notification_to_fail(notification2)
126
159
 
160
+ expect(failed_ids).to_not include(notification1.id)
127
161
  notification1.reload
128
162
  notification1.delivered.should be_true
129
163
  end
130
164
 
131
- # Unreliable.
132
- # it 'marks notifications following the failed one as retryable' do
133
- # Rpush.config.push_poll = 1_000_000
134
- #
135
- # notifications.each { |n| wait_for_notification_to_deliver(n) }
136
- # fail_notification(notification2)
137
- #
138
- # [notification3, notification4].each do |n|
139
- # wait_for_notification_to_retry(n)
140
- # end
141
- # end
142
-
143
- describe 'without an error response' do
144
- it 'marks all notifications as failed' do
145
- notifications.each { |n| wait_for_notification_to_deliver(n) }
146
- ssl_socket.stub(read: nil)
147
- enable_io_select
148
- notifications.each { |n| wait_for_notification_to_fail(n) }
149
- end
165
+ it 'marks notifications following the failed one as retryable' do
166
+ notifications.each { |n| wait_for_notification_to_deliver(n) }
167
+ fail_notification(notification2)
168
+ [notification3, notification4].each { |n| wait_for_notification_to_retry(n) }
150
169
  end
151
170
  end
152
171
  end
@@ -36,4 +36,13 @@ describe 'GCM' do
36
36
  notification.reload
37
37
  end.to_not change(notification, :delivered).to(true)
38
38
  end
39
+
40
+ it 'retries notification that fail due to a SocketError' do
41
+ expect(http).to receive(:request).and_raise(SocketError.new)
42
+ expect(notification.deliver_after).to be_nil
43
+ expect do
44
+ Rpush.push
45
+ notification.reload
46
+ end.to change(notification, :deliver_after).to(kind_of(Time))
47
+ end
39
48
  end