rapns_rails_2 3.4.3

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 (113) hide show
  1. checksums.yaml +15 -0
  2. data/CHANGELOG.md +83 -0
  3. data/LICENSE +7 -0
  4. data/README.md +168 -0
  5. data/bin/rapns +37 -0
  6. data/config/database.yml +44 -0
  7. data/lib/generators/rapns_generator.rb +25 -0
  8. data/lib/generators/templates/add_alert_is_json_to_rapns_notifications.rb +9 -0
  9. data/lib/generators/templates/add_app_to_rapns.rb +11 -0
  10. data/lib/generators/templates/add_gcm.rb +95 -0
  11. data/lib/generators/templates/create_rapns_apps.rb +16 -0
  12. data/lib/generators/templates/create_rapns_feedback.rb +15 -0
  13. data/lib/generators/templates/create_rapns_notifications.rb +26 -0
  14. data/lib/generators/templates/rapns.rb +87 -0
  15. data/lib/rapns/TODO +3 -0
  16. data/lib/rapns/apns/app.rb +25 -0
  17. data/lib/rapns/apns/binary_notification_validator.rb +12 -0
  18. data/lib/rapns/apns/device_token_format_validator.rb +12 -0
  19. data/lib/rapns/apns/feedback.rb +16 -0
  20. data/lib/rapns/apns/notification.rb +91 -0
  21. data/lib/rapns/apns_feedback.rb +13 -0
  22. data/lib/rapns/app.rb +16 -0
  23. data/lib/rapns/configuration.rb +89 -0
  24. data/lib/rapns/daemon/apns/app_runner.rb +26 -0
  25. data/lib/rapns/daemon/apns/certificate_expired_error.rb +20 -0
  26. data/lib/rapns/daemon/apns/connection.rb +142 -0
  27. data/lib/rapns/daemon/apns/delivery.rb +64 -0
  28. data/lib/rapns/daemon/apns/delivery_handler.rb +35 -0
  29. data/lib/rapns/daemon/apns/disconnection_error.rb +20 -0
  30. data/lib/rapns/daemon/apns/feedback_receiver.rb +89 -0
  31. data/lib/rapns/daemon/app_runner.rb +179 -0
  32. data/lib/rapns/daemon/batch.rb +112 -0
  33. data/lib/rapns/daemon/delivery.rb +23 -0
  34. data/lib/rapns/daemon/delivery_error.rb +19 -0
  35. data/lib/rapns/daemon/delivery_handler.rb +52 -0
  36. data/lib/rapns/daemon/delivery_handler_collection.rb +33 -0
  37. data/lib/rapns/daemon/feeder.rb +65 -0
  38. data/lib/rapns/daemon/gcm/app_runner.rb +13 -0
  39. data/lib/rapns/daemon/gcm/delivery.rb +228 -0
  40. data/lib/rapns/daemon/gcm/delivery_handler.rb +20 -0
  41. data/lib/rapns/daemon/interruptible_sleep.rb +65 -0
  42. data/lib/rapns/daemon/reflectable.rb +13 -0
  43. data/lib/rapns/daemon/store/active_record/reconnectable.rb +66 -0
  44. data/lib/rapns/daemon/store/active_record.rb +128 -0
  45. data/lib/rapns/daemon.rb +129 -0
  46. data/lib/rapns/deprecatable.rb +23 -0
  47. data/lib/rapns/deprecation.rb +23 -0
  48. data/lib/rapns/embed.rb +28 -0
  49. data/lib/rapns/gcm/app.rb +7 -0
  50. data/lib/rapns/gcm/expiry_collapse_key_mutual_inclusion_validator.rb +11 -0
  51. data/lib/rapns/gcm/notification.rb +37 -0
  52. data/lib/rapns/gcm/payload_data_size_validator.rb +13 -0
  53. data/lib/rapns/gcm/registration_ids_count_validator.rb +13 -0
  54. data/lib/rapns/logger.rb +76 -0
  55. data/lib/rapns/multi_json_helper.rb +16 -0
  56. data/lib/rapns/notification.rb +62 -0
  57. data/lib/rapns/notifier.rb +35 -0
  58. data/lib/rapns/push.rb +17 -0
  59. data/lib/rapns/rails-2-compatibility.rb +34 -0
  60. data/lib/rapns/reflection.rb +44 -0
  61. data/lib/rapns/upgraded.rb +31 -0
  62. data/lib/rapns/version.rb +3 -0
  63. data/lib/rapns_rails_2.rb +67 -0
  64. data/lib/tasks/cane.rake +18 -0
  65. data/lib/tasks/test.rake +38 -0
  66. data/spec/support/cert_with_password.pem +90 -0
  67. data/spec/support/cert_without_password.pem +59 -0
  68. data/spec/support/simplecov_helper.rb +13 -0
  69. data/spec/support/simplecov_quality_formatter.rb +8 -0
  70. data/spec/tmp/.gitkeep +0 -0
  71. data/spec/unit/apns/app_spec.rb +29 -0
  72. data/spec/unit/apns/feedback_spec.rb +9 -0
  73. data/spec/unit/apns/notification_spec.rb +215 -0
  74. data/spec/unit/apns_feedback_spec.rb +21 -0
  75. data/spec/unit/app_spec.rb +16 -0
  76. data/spec/unit/configuration_spec.rb +55 -0
  77. data/spec/unit/daemon/apns/app_runner_spec.rb +45 -0
  78. data/spec/unit/daemon/apns/certificate_expired_error_spec.rb +11 -0
  79. data/spec/unit/daemon/apns/connection_spec.rb +287 -0
  80. data/spec/unit/daemon/apns/delivery_handler_spec.rb +59 -0
  81. data/spec/unit/daemon/apns/delivery_spec.rb +101 -0
  82. data/spec/unit/daemon/apns/disconnection_error_spec.rb +18 -0
  83. data/spec/unit/daemon/apns/feedback_receiver_spec.rb +134 -0
  84. data/spec/unit/daemon/app_runner_shared.rb +83 -0
  85. data/spec/unit/daemon/app_runner_spec.rb +170 -0
  86. data/spec/unit/daemon/batch_spec.rb +219 -0
  87. data/spec/unit/daemon/delivery_error_spec.rb +13 -0
  88. data/spec/unit/daemon/delivery_handler_collection_spec.rb +37 -0
  89. data/spec/unit/daemon/delivery_handler_shared.rb +45 -0
  90. data/spec/unit/daemon/feeder_spec.rb +81 -0
  91. data/spec/unit/daemon/gcm/app_runner_spec.rb +19 -0
  92. data/spec/unit/daemon/gcm/delivery_handler_spec.rb +44 -0
  93. data/spec/unit/daemon/gcm/delivery_spec.rb +289 -0
  94. data/spec/unit/daemon/interruptible_sleep_spec.rb +68 -0
  95. data/spec/unit/daemon/reflectable_spec.rb +27 -0
  96. data/spec/unit/daemon/store/active_record/reconnectable_spec.rb +114 -0
  97. data/spec/unit/daemon/store/active_record_spec.rb +281 -0
  98. data/spec/unit/daemon_spec.rb +157 -0
  99. data/spec/unit/deprecatable_spec.rb +32 -0
  100. data/spec/unit/deprecation_spec.rb +15 -0
  101. data/spec/unit/embed_spec.rb +50 -0
  102. data/spec/unit/gcm/app_spec.rb +4 -0
  103. data/spec/unit/gcm/notification_spec.rb +52 -0
  104. data/spec/unit/logger_spec.rb +180 -0
  105. data/spec/unit/notification_shared.rb +45 -0
  106. data/spec/unit/notification_spec.rb +4 -0
  107. data/spec/unit/notifier_spec.rb +32 -0
  108. data/spec/unit/push_spec.rb +44 -0
  109. data/spec/unit/rapns_spec.rb +9 -0
  110. data/spec/unit/reflection_spec.rb +30 -0
  111. data/spec/unit/upgraded_spec.rb +40 -0
  112. data/spec/unit_spec_helper.rb +137 -0
  113. metadata +232 -0
@@ -0,0 +1,228 @@
1
+ module Rapns
2
+ module Daemon
3
+ module Gcm
4
+ # http://developer.android.com/guide/google/gcm/gcm.html#response
5
+ class Delivery < Rapns::Daemon::Delivery
6
+ include Rapns::MultiJsonHelper
7
+
8
+ GCM_URI = URI.parse('https://android.googleapis.com/gcm/send')
9
+ UNAVAILABLE_STATES = ['Unavailable', 'InternalServerError']
10
+ INVALID_REGISTRATION_ID_STATES = ['InvalidRegistration', 'MismatchSenderId', 'NotRegistered', 'InvalidPackageName']
11
+
12
+ def initialize(app, http, notification, batch)
13
+ @app = app
14
+ @http = http
15
+ @notification = notification
16
+ @batch = batch
17
+ end
18
+
19
+ def perform
20
+ begin
21
+ handle_response(do_post)
22
+ rescue Rapns::DeliveryError => error
23
+ mark_failed(error.code, error.description)
24
+ raise
25
+ end
26
+ end
27
+
28
+ protected
29
+
30
+ def handle_response(response)
31
+ case response.code.to_i
32
+ when 200
33
+ ok(response)
34
+ when 400
35
+ bad_request(response)
36
+ when 401
37
+ unauthorized(response)
38
+ when 500
39
+ internal_server_error(response)
40
+ when 503
41
+ service_unavailable(response)
42
+ else
43
+ raise Rapns::DeliveryError.new(response.code, @notification.id, HTTP_STATUS_CODES[response.code.to_i])
44
+ end
45
+ end
46
+
47
+ def ok(response)
48
+ body = multi_json_load(response.body)
49
+ if body['failure'].to_i == 0
50
+ mark_delivered
51
+ Rapns.logger.info("[#{@app.name}] #{@notification.id} sent to #{@notification.registration_ids.join(', ')}")
52
+ else
53
+ handle_invalid_registration_ids(response, body)
54
+ handle_errors(response, body)
55
+ end
56
+
57
+ handle_canonical_ids(response, body)
58
+ end
59
+
60
+ def handle_errors(response, body)
61
+ errors = {}
62
+
63
+ body['results'].each_with_index do |result, i|
64
+ errors[i] = result['error'] if result['error'] && ! INVALID_REGISTRATION_ID_STATES.include?(result['error'])
65
+ end
66
+ return if errors.empty?
67
+
68
+ if body['success'].to_i == 0 && errors.values.all? { |error| UNAVAILABLE_STATES.include?(error) }
69
+ all_devices_unavailable(response)
70
+ elsif errors.values.any? { |error| UNAVAILABLE_STATES.include?(error) }
71
+ some_devices_unavailable(response, errors)
72
+ else
73
+ raise Rapns::DeliveryError.new(nil, @notification.id, describe_errors(errors))
74
+ end
75
+ end
76
+
77
+ def handle_invalid_registration_ids(response, body)
78
+ body['results'].each_with_index do |result, i|
79
+ next unless INVALID_REGISTRATION_ID_STATES.include?(result['error'])
80
+
81
+ registration_id = @notification.registration_ids[i]
82
+ reflect(:gcm_invalid_registration_id, @app, result['error'], registration_id)
83
+ end
84
+ end
85
+
86
+ def handle_canonical_ids(response, body)
87
+ if body['canonical_ids'] && body['canonical_ids'].to_i > 0
88
+ body['results'].each_with_index do |result, i|
89
+ if result['message_id'] && result['registration_id']
90
+ old_id = @notification.registration_ids[i]
91
+ reflect(:gcm_canonical_id, old_id, result['registration_id'])
92
+ end
93
+ end
94
+ end
95
+ end
96
+
97
+ def bad_request(response)
98
+ raise Rapns::DeliveryError.new(400, @notification.id, 'GCM failed to parse the JSON request. Possibly an rapns bug, please open an issue.')
99
+ end
100
+
101
+ def unauthorized(response)
102
+ raise Rapns::DeliveryError.new(401, @notification.id, 'Unauthorized, check your App auth_key.')
103
+ end
104
+
105
+ def internal_server_error(response)
106
+ retry_delivery(@notification, response)
107
+ Rapns.logger.warn("GCM responded with an Internal Error. " + retry_message)
108
+ end
109
+
110
+ def service_unavailable(response)
111
+ retry_delivery(@notification, response)
112
+ Rapns.logger.warn("GCM responded with an Service Unavailable Error. " + retry_message)
113
+ end
114
+
115
+ def all_devices_unavailable(response)
116
+ retry_delivery(@notification, response)
117
+ Rapns.logger.warn("All recipients unavailable. " + retry_message)
118
+ end
119
+
120
+ def some_devices_unavailable(response, errors)
121
+ unavailable_idxs = errors.find_all { |i, error| UNAVAILABLE_STATES.include?(error) }.map(&:first)
122
+ new_notification = create_new_notification(response, unavailable_idxs)
123
+ raise Rapns::DeliveryError.new(nil, @notification.id,
124
+ describe_errors(errors) + " #{unavailable_idxs.join(', ')} will be retried as notification #{new_notification.id}.")
125
+ end
126
+
127
+ def create_new_notification(response, unavailable_idxs)
128
+ attrs = @notification.attributes.slice('app_id', 'collapse_key', 'delay_while_idle')
129
+ registration_ids = unavailable_idxs.map { |i| @notification.registration_ids[i] }
130
+ Rapns::Daemon.store.create_gcm_notification(attrs, @notification.data,
131
+ registration_ids, deliver_after_header(response), @notification.app)
132
+ end
133
+
134
+ def deliver_after_header(response)
135
+ if response.header['retry-after']
136
+ if response.header['retry-after'].to_s =~ /^[0-9]+$/
137
+ Time.now + response.header['retry-after'].to_i
138
+ else
139
+ Time.httpdate(response.header['retry-after'])
140
+ end
141
+ end
142
+ end
143
+
144
+ def retry_delivery(notification, response)
145
+ if time = deliver_after_header(response)
146
+ mark_retryable(notification, time)
147
+ else
148
+ mark_retryable_exponential(notification)
149
+ end
150
+ end
151
+
152
+ def describe_errors(errors)
153
+ description = if errors.size == @notification.registration_ids.size
154
+ "Failed to deliver to all recipients. Errors: #{errors.values.join(', ')}."
155
+ else
156
+ "Failed to deliver to recipients #{errors.keys.join(', ')}. Errors: #{errors.values.join(', ')}."
157
+ end
158
+ end
159
+
160
+ def retry_message
161
+ "Notification #{@notification.id} will be retried after #{@notification.deliver_after.strftime("%Y-%m-%d %H:%M:%S")} (retry #{@notification.retries})."
162
+ end
163
+
164
+ def do_post
165
+ post = Net::HTTP::Post.new(GCM_URI.path, initheader = {'Content-Type' => 'application/json',
166
+ 'Authorization' => "key=#{@notification.app.auth_key}"})
167
+ post.body = @notification.as_json.to_json
168
+ @http.request(GCM_URI, post)
169
+ end
170
+
171
+ HTTP_STATUS_CODES = {
172
+ 100 => 'Continue',
173
+ 101 => 'Switching Protocols',
174
+ 102 => 'Processing',
175
+ 200 => 'OK',
176
+ 201 => 'Created',
177
+ 202 => 'Accepted',
178
+ 203 => 'Non-Authoritative Information',
179
+ 204 => 'No Content',
180
+ 205 => 'Reset Content',
181
+ 206 => 'Partial Content',
182
+ 207 => 'Multi-Status',
183
+ 226 => 'IM Used',
184
+ 300 => 'Multiple Choices',
185
+ 301 => 'Moved Permanently',
186
+ 302 => 'Found',
187
+ 303 => 'See Other',
188
+ 304 => 'Not Modified',
189
+ 305 => 'Use Proxy',
190
+ 306 => 'Reserved',
191
+ 307 => 'Temporary Redirect',
192
+ 400 => 'Bad Request',
193
+ 401 => 'Unauthorized',
194
+ 402 => 'Payment Required',
195
+ 403 => 'Forbidden',
196
+ 404 => 'Not Found',
197
+ 405 => 'Method Not Allowed',
198
+ 406 => 'Not Acceptable',
199
+ 407 => 'Proxy Authentication Required',
200
+ 408 => 'Request Timeout',
201
+ 409 => 'Conflict',
202
+ 410 => 'Gone',
203
+ 411 => 'Length Required',
204
+ 412 => 'Precondition Failed',
205
+ 413 => 'Request Entity Too Large',
206
+ 414 => 'Request-URI Too Long',
207
+ 415 => 'Unsupported Media Type',
208
+ 416 => 'Requested Range Not Satisfiable',
209
+ 417 => 'Expectation Failed',
210
+ 418 => "I'm a Teapot",
211
+ 422 => 'Unprocessable Entity',
212
+ 423 => 'Locked',
213
+ 424 => 'Failed Dependency',
214
+ 426 => 'Upgrade Required',
215
+ 500 => 'Internal Server Error',
216
+ 501 => 'Not Implemented',
217
+ 502 => 'Bad Gateway',
218
+ 503 => 'Service Unavailable',
219
+ 504 => 'Gateway Timeout',
220
+ 505 => 'HTTP Version Not Supported',
221
+ 506 => 'Variant Also Negotiates',
222
+ 507 => 'Insufficient Storage',
223
+ 510 => 'Not Extended',
224
+ }
225
+ end
226
+ end
227
+ end
228
+ end
@@ -0,0 +1,20 @@
1
+ module Rapns
2
+ module Daemon
3
+ module Gcm
4
+ class DeliveryHandler < Rapns::Daemon::DeliveryHandler
5
+ def initialize(app)
6
+ @app = app
7
+ @http = Net::HTTP::Persistent.new('rapns')
8
+ end
9
+
10
+ def deliver(notification, batch)
11
+ Rapns::Daemon::Gcm::Delivery.new(@app, @http, notification, batch).perform
12
+ end
13
+
14
+ def stopped
15
+ @http.shutdown
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,65 @@
1
+ module Rapns
2
+ module Daemon
3
+ class InterruptibleSleep
4
+
5
+ def initialize
6
+ @sleep_reader, @wake_writer = IO.pipe
7
+ end
8
+
9
+ # enable wake on receiving udp packets at the given address and port
10
+ # this returns the host,port used by bind in case an ephemeral port
11
+ # was indicated by specifying 0 as the port number.
12
+ # @return [String,Integer] host,port of bound UDP socket.
13
+ def enable_wake_on_udp(host, port)
14
+ @udp_wakeup = UDPSocket.new
15
+ @udp_wakeup.bind(host, port)
16
+ @udp_wakeup.addr.values_at(3,1)
17
+ end
18
+
19
+ # wait for the given timeout in seconds, or data was written to the pipe
20
+ # or the udp wakeup port if enabled.
21
+ # @return [boolean] true if the sleep was interrupted, or false
22
+ def sleep(timeout)
23
+ read_ports = [@sleep_reader]
24
+ read_ports << @udp_wakeup if @udp_wakeup
25
+ rs, = IO.select(read_ports, nil, nil, timeout) rescue nil
26
+
27
+ # consume all data on the readable io's so that our next call will wait for more data
28
+ if rs && rs.include?(@sleep_reader)
29
+ while true
30
+ begin
31
+ @sleep_reader.read_nonblock(1)
32
+ rescue Errno::EWOULDBLOCK, Errno::EAGAIN
33
+ # rescue IO::WaitReadable
34
+ break
35
+ end
36
+ end
37
+ end
38
+
39
+ if rs && rs.include?(@udp_wakeup)
40
+ while true
41
+ begin
42
+ @udp_wakeup.recv_nonblock(1)
43
+ rescue Errno::EWOULDBLOCK, Errno::EAGAIN
44
+ # rescue IO::WaitReadable
45
+ break
46
+ end
47
+ end
48
+ end
49
+
50
+ !rs.nil? && rs.any?
51
+ end
52
+
53
+ # writing to the pipe will wake the sleeping thread
54
+ def interrupt_sleep
55
+ @wake_writer.write('.')
56
+ end
57
+
58
+ def close
59
+ @sleep_reader.close rescue nil
60
+ @wake_writer.close rescue nil
61
+ @udp_wakeup.close if @udp_wakeup rescue nil
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,13 @@
1
+ module Rapns
2
+ module Daemon
3
+ module Reflectable
4
+ def reflect(name, *args)
5
+ begin
6
+ Rapns.reflections.__dispatch(name, *args)
7
+ rescue StandardError => e
8
+ Rapns.logger.error(e)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,66 @@
1
+ class PGError < StandardError; end if !defined?(PGError)
2
+ class Mysql; class Error < StandardError; end; end if !defined?(Mysql)
3
+ module Mysql2; class Error < StandardError; end; end if !defined?(Mysql2)
4
+ module ActiveRecord; end
5
+ class ActiveRecord::JDBCError < StandardError; end if !defined?(::ActiveRecord::JDBCError)
6
+ if !defined?(::SQLite3::Exception)
7
+ module SQLite3
8
+ class Exception < StandardError; end
9
+ end
10
+ end
11
+
12
+ module Rapns
13
+ module Daemon
14
+ module Store
15
+ class ActiveRecord
16
+ module Reconnectable
17
+ ADAPTER_ERRORS = [::ActiveRecord::StatementInvalid, PGError, Mysql::Error,
18
+ Mysql2::Error, ::ActiveRecord::JDBCError, SQLite3::Exception]
19
+
20
+ def with_database_reconnect_and_retry
21
+ begin
22
+ ::ActiveRecord::Base.connection_pool.with_connection do
23
+ yield
24
+ end
25
+ rescue *ADAPTER_ERRORS => e
26
+ Rapns.logger.error(e)
27
+ database_connection_lost
28
+ retry
29
+ end
30
+ end
31
+
32
+ def database_connection_lost
33
+ Rapns.logger.warn("Lost connection to database, reconnecting...")
34
+ attempts = 0
35
+ loop do
36
+ begin
37
+ Rapns.logger.warn("Attempt #{attempts += 1}")
38
+ reconnect_database
39
+ check_database_is_connected
40
+ break
41
+ rescue *ADAPTER_ERRORS => e
42
+ Rapns.logger.error(e, :airbrake_notify => false)
43
+ sleep_to_avoid_thrashing
44
+ end
45
+ end
46
+ Rapns.logger.warn("Database reconnected")
47
+ end
48
+
49
+ def reconnect_database
50
+ ::ActiveRecord::Base.clear_all_connections!
51
+ ::ActiveRecord::Base.establish_connection
52
+ end
53
+
54
+ def check_database_is_connected
55
+ # Simply asking the adapter for the connection state is not sufficient.
56
+ Rapns::Notification.count
57
+ end
58
+
59
+ def sleep_to_avoid_thrashing
60
+ sleep 2
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,128 @@
1
+ require 'active_record'
2
+
3
+ require 'rapns/daemon/store/active_record/reconnectable'
4
+
5
+ module Rapns
6
+ module Daemon
7
+ module Store
8
+ class ActiveRecord
9
+ include Reconnectable
10
+
11
+ def deliverable_notifications(apps)
12
+ with_database_reconnect_and_retry do
13
+ batch_size = Rapns.config.batch_size
14
+ relation = Rapns::Notification.ready_for_delivery.for_apps(apps)
15
+ if Rapns.config.push
16
+ relation.all
17
+ else
18
+ relation.find(:all, :limit => batch_size)
19
+ end
20
+ end
21
+ end
22
+
23
+ def mark_retryable(notification, deliver_after)
24
+ with_database_reconnect_and_retry do
25
+ notification.retries += 1
26
+ notification.deliver_after = deliver_after
27
+ notification.save(false)
28
+ end
29
+ end
30
+
31
+ def mark_batch_retryable(notifications, deliver_after)
32
+ ids = []
33
+ notifications.each do |n|
34
+ # Update attrs for reflections, but don't save.
35
+ n.retries += 1
36
+ n.deliver_after = deliver_after
37
+ ids << n.id
38
+ end
39
+ with_database_reconnect_and_retry do
40
+ Rapns::Notification.find(ids).each { |n|
41
+ n.update_attributes(:retries => n.retries + 1, :deliver_after => deliver_after)
42
+ }
43
+ end
44
+ end
45
+
46
+ def mark_delivered(notification)
47
+ with_database_reconnect_and_retry do
48
+ notification.delivered = true
49
+ notification.delivered_at = Time.now
50
+ notification.save(false)
51
+ end
52
+ end
53
+
54
+ def mark_batch_delivered(notifications)
55
+ now = Time.now
56
+ ids = []
57
+ notifications.each do |n|
58
+ # Update attrs for reflections, but don't save.
59
+ n.delivered = true
60
+ n.delivered_at = now
61
+ ids << n.id
62
+ end
63
+ with_database_reconnect_and_retry do
64
+ Rapns::Notification.find(ids).each { |n|
65
+ n.update_attributes(:delivered => true, :delivered_at => now)
66
+ }
67
+ end
68
+ end
69
+
70
+ def mark_failed(notification, code, description)
71
+ with_database_reconnect_and_retry do
72
+ notification.delivered = false
73
+ notification.delivered_at = nil
74
+ notification.failed = true
75
+ notification.failed_at = Time.now
76
+ notification.error_code = code
77
+ notification.error_description = description
78
+ notification.save(false)
79
+ end
80
+ end
81
+
82
+ def mark_batch_failed(notifications, code, description)
83
+ now = Time.now
84
+ ids = []
85
+ notifications.each do |n|
86
+ # Update attrs for reflections, but don't save.
87
+ n.delivered = false
88
+ n.delivered_at = nil
89
+ n.failed = true
90
+ n.failed_at = now
91
+ n.error_code = code
92
+ n.error_description = description
93
+ ids << n.id
94
+ end
95
+ with_database_reconnect_and_retry do
96
+ Rapns::Notification.find(ids).each { |n|
97
+ n.update_attributes(:delivered => false, :delivered_at => nil, :failed => true, :failed_at => now, :error_code => code, :error_description => description)
98
+ }
99
+ end
100
+ end
101
+
102
+ def create_apns_feedback(failed_at, device_token, app)
103
+ with_database_reconnect_and_retry do
104
+ Rapns::Apns::Feedback.create!(:failed_at => failed_at,
105
+ :device_token => device_token, :app => app)
106
+ end
107
+ end
108
+
109
+ def create_gcm_notification(attrs, data, registration_ids, deliver_after, app)
110
+ with_database_reconnect_and_retry do
111
+ notification = Rapns::Gcm::Notification.new
112
+ notification.attributes = attrs
113
+ notification.data = data
114
+ notification.registration_ids = registration_ids
115
+ notification.deliver_after = deliver_after
116
+ notification.app = app
117
+ notification.save!
118
+ notification
119
+ end
120
+ end
121
+
122
+ def after_daemonize
123
+ reconnect_database
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,129 @@
1
+ require 'thread'
2
+ require 'socket'
3
+ require 'pathname'
4
+ require 'openssl'
5
+
6
+ require 'net/http/persistent'
7
+
8
+ require 'rapns/daemon/reflectable'
9
+ require 'rapns/daemon/interruptible_sleep'
10
+ require 'rapns/daemon/delivery_error'
11
+ require 'rapns/daemon/delivery'
12
+ require 'rapns/daemon/feeder'
13
+ require 'rapns/daemon/batch'
14
+ require 'rapns/daemon/app_runner'
15
+ require 'rapns/daemon/delivery_handler'
16
+ require 'rapns/daemon/delivery_handler_collection'
17
+
18
+ require 'rapns/daemon/apns/delivery'
19
+ require 'rapns/daemon/apns/disconnection_error'
20
+ require 'rapns/daemon/apns/certificate_expired_error'
21
+ require 'rapns/daemon/apns/connection'
22
+ require 'rapns/daemon/apns/app_runner'
23
+ require 'rapns/daemon/apns/delivery_handler'
24
+ require 'rapns/daemon/apns/feedback_receiver'
25
+
26
+ require 'rapns/daemon/gcm/delivery'
27
+ require 'rapns/daemon/gcm/app_runner'
28
+ require 'rapns/daemon/gcm/delivery_handler'
29
+
30
+ module Rapns
31
+ module Daemon
32
+ class << self
33
+ attr_accessor :store
34
+ end
35
+
36
+ def self.start
37
+ setup_signal_traps if trap_signals?
38
+
39
+ initialize_store
40
+ return unless store
41
+
42
+ if daemonize?
43
+ daemonize
44
+ store.after_daemonize
45
+ end
46
+
47
+ write_pid_file
48
+ Upgraded.check(:exit => true)
49
+ AppRunner.sync
50
+ Feeder.start
51
+ end
52
+
53
+ def self.shutdown(quiet = false)
54
+ puts "\nShutting down..." unless quiet
55
+ Feeder.stop
56
+ AppRunner.stop
57
+ delete_pid_file
58
+ end
59
+
60
+ def self.initialize_store
61
+ return if store
62
+ begin
63
+ require "rapns/daemon/store/#{Rapns.config.store}"
64
+ klass = "Rapns::Daemon::Store::#{Rapns.config.store.to_s.camelcase}".constantize
65
+ self.store = klass.new
66
+ rescue StandardError, LoadError => e
67
+ Rapns.logger.error("Failed to load '#{Rapns.config.store}' storage backend.")
68
+ Rapns.logger.error(e)
69
+ end
70
+ end
71
+
72
+ protected
73
+
74
+ def self.daemonize?
75
+ !(Rapns.config.foreground || Rapns.config.embedded || Rapns.jruby?)
76
+ end
77
+
78
+ def self.trap_signals?
79
+ !Rapns.config.embedded
80
+ end
81
+
82
+ def self.setup_signal_traps
83
+ @shutting_down = false
84
+
85
+ Signal.trap('SIGHUP') { AppRunner.sync }
86
+ Signal.trap('SIGUSR2') { AppRunner.debug }
87
+
88
+ ['SIGINT', 'SIGTERM'].each do |signal|
89
+ Signal.trap(signal) { handle_shutdown_signal }
90
+ end
91
+ end
92
+
93
+ def self.handle_shutdown_signal
94
+ exit 1 if @shutting_down
95
+ @shutting_down = true
96
+ shutdown
97
+ end
98
+
99
+ def self.write_pid_file
100
+ if !Rapns.config.pid_file.blank?
101
+ begin
102
+ File.open(Rapns.config.pid_file, 'w') { |f| f.puts Process.pid }
103
+ rescue SystemCallError => e
104
+ Rapns.logger.error("Failed to write PID to '#{Rapns.config.pid_file}': #{e.inspect}")
105
+ end
106
+ end
107
+ end
108
+
109
+ def self.delete_pid_file
110
+ pid_file = Rapns.config.pid_file
111
+ File.delete(pid_file) if !pid_file.blank? && File.exists?(pid_file)
112
+ end
113
+
114
+ # :nocov:
115
+ def self.daemonize
116
+ if RUBY_VERSION < "1.9"
117
+ exit if fork
118
+ Process.setsid
119
+ exit if fork
120
+ Dir.chdir "/"
121
+ STDIN.reopen "/dev/null"
122
+ STDOUT.reopen "/dev/null", "a"
123
+ STDERR.reopen "/dev/null", "a"
124
+ else
125
+ Process.daemon
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,23 @@
1
+ module Rapns
2
+ module Deprecatable
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ end
6
+
7
+ module ClassMethods
8
+ def deprecated(method_name, version, msg=nil)
9
+ instance_eval do
10
+ alias_method "#{method_name}_without_warning", method_name
11
+ end
12
+ warning = "#{method_name} is deprecated and will be removed from Rapns #{version}."
13
+ warning << " #{msg}" if msg
14
+ class_eval(<<-RUBY, __FILE__, __LINE__)
15
+ def #{method_name}(*args, &blk)
16
+ Rapns::Deprecation.warn(#{warning.inspect})
17
+ #{method_name}_without_warning(*args, &blk)
18
+ end
19
+ RUBY
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ module Rapns
2
+ class Deprecation
3
+ def self.muted
4
+ begin
5
+ orig_val = Thread.current[:rapns_mute_deprecations]
6
+ Thread.current[:rapns_mute_deprecations] = true
7
+ yield
8
+ ensure
9
+ Thread.current[:rapns_mute_deprecations] = orig_val
10
+ end
11
+ end
12
+
13
+ def self.muted?
14
+ Thread.current[:rapns_mute_deprecations] == true
15
+ end
16
+
17
+ def self.warn(msg)
18
+ unless Rapns::Deprecation.muted?
19
+ STDERR.puts "DEPRECATION WARNING: #{msg}"
20
+ end
21
+ end
22
+ end
23
+ end