rpush 2.2.0-java → 2.3.0-java

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.
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 +24 -21
  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 +13 -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 +34 -36
  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,13 @@
1
+ module Rpush
2
+ module Reflectable
3
+ def reflect(name, *args)
4
+ Rpush.reflection_stack.each do |reflection_collection|
5
+ begin
6
+ reflection_collection.__dispatch(name, *args)
7
+ rescue StandardError => e
8
+ Rpush.logger.error(e)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ 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'
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