rpush 1.0.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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +99 -0
- data/LICENSE +7 -0
- data/README.md +189 -0
- data/bin/rpush +36 -0
- data/config/database.yml +44 -0
- data/lib/generators/rpush_generator.rb +44 -0
- data/lib/generators/templates/add_adm.rb +23 -0
- data/lib/generators/templates/add_alert_is_json_to_rapns_notifications.rb +9 -0
- data/lib/generators/templates/add_app_to_rapns.rb +11 -0
- data/lib/generators/templates/add_fail_after_to_rpush_notifications.rb +9 -0
- data/lib/generators/templates/add_gcm.rb +102 -0
- data/lib/generators/templates/add_rpush.rb +349 -0
- data/lib/generators/templates/add_wpns.rb +16 -0
- data/lib/generators/templates/create_rapns_apps.rb +16 -0
- data/lib/generators/templates/create_rapns_feedback.rb +18 -0
- data/lib/generators/templates/create_rapns_notifications.rb +29 -0
- data/lib/generators/templates/rename_rapns_to_rpush.rb +63 -0
- data/lib/generators/templates/rpush.rb +104 -0
- data/lib/rpush/TODO +3 -0
- data/lib/rpush/adm/app.rb +15 -0
- data/lib/rpush/adm/data_validator.rb +11 -0
- data/lib/rpush/adm/notification.rb +29 -0
- data/lib/rpush/apns/app.rb +29 -0
- data/lib/rpush/apns/binary_notification_validator.rb +12 -0
- data/lib/rpush/apns/device_token_format_validator.rb +12 -0
- data/lib/rpush/apns/feedback.rb +16 -0
- data/lib/rpush/apns/notification.rb +84 -0
- data/lib/rpush/apns_feedback.rb +13 -0
- data/lib/rpush/app.rb +18 -0
- data/lib/rpush/configuration.rb +75 -0
- data/lib/rpush/daemon/adm/delivery.rb +222 -0
- data/lib/rpush/daemon/adm.rb +9 -0
- data/lib/rpush/daemon/apns/certificate_expired_error.rb +20 -0
- data/lib/rpush/daemon/apns/delivery.rb +64 -0
- data/lib/rpush/daemon/apns/disconnection_error.rb +20 -0
- data/lib/rpush/daemon/apns/feedback_receiver.rb +79 -0
- data/lib/rpush/daemon/apns.rb +16 -0
- data/lib/rpush/daemon/app_runner.rb +187 -0
- data/lib/rpush/daemon/batch.rb +115 -0
- data/lib/rpush/daemon/constants.rb +59 -0
- data/lib/rpush/daemon/delivery.rb +28 -0
- data/lib/rpush/daemon/delivery_error.rb +19 -0
- data/lib/rpush/daemon/dispatcher/http.rb +21 -0
- data/lib/rpush/daemon/dispatcher/tcp.rb +30 -0
- data/lib/rpush/daemon/dispatcher_loop.rb +54 -0
- data/lib/rpush/daemon/dispatcher_loop_collection.rb +33 -0
- data/lib/rpush/daemon/feeder.rb +68 -0
- data/lib/rpush/daemon/gcm/delivery.rb +222 -0
- data/lib/rpush/daemon/gcm.rb +9 -0
- data/lib/rpush/daemon/interruptible_sleep.rb +61 -0
- data/lib/rpush/daemon/loggable.rb +31 -0
- data/lib/rpush/daemon/reflectable.rb +13 -0
- data/lib/rpush/daemon/retry_header_parser.rb +23 -0
- data/lib/rpush/daemon/retryable_error.rb +20 -0
- data/lib/rpush/daemon/service_config_methods.rb +33 -0
- data/lib/rpush/daemon/store/active_record/reconnectable.rb +68 -0
- data/lib/rpush/daemon/store/active_record.rb +154 -0
- data/lib/rpush/daemon/tcp_connection.rb +143 -0
- data/lib/rpush/daemon/too_many_requests_error.rb +20 -0
- data/lib/rpush/daemon/wpns/delivery.rb +132 -0
- data/lib/rpush/daemon/wpns.rb +9 -0
- data/lib/rpush/daemon.rb +140 -0
- data/lib/rpush/deprecatable.rb +23 -0
- data/lib/rpush/deprecation.rb +23 -0
- data/lib/rpush/embed.rb +28 -0
- data/lib/rpush/gcm/app.rb +11 -0
- data/lib/rpush/gcm/expiry_collapse_key_mutual_inclusion_validator.rb +11 -0
- data/lib/rpush/gcm/notification.rb +30 -0
- data/lib/rpush/logger.rb +63 -0
- data/lib/rpush/multi_json_helper.rb +16 -0
- data/lib/rpush/notification.rb +69 -0
- data/lib/rpush/notifier.rb +52 -0
- data/lib/rpush/payload_data_size_validator.rb +10 -0
- data/lib/rpush/push.rb +16 -0
- data/lib/rpush/railtie.rb +11 -0
- data/lib/rpush/reflection.rb +58 -0
- data/lib/rpush/registration_ids_count_validator.rb +10 -0
- data/lib/rpush/version.rb +3 -0
- data/lib/rpush/wpns/app.rb +9 -0
- data/lib/rpush/wpns/notification.rb +26 -0
- data/lib/rpush.rb +62 -0
- data/lib/tasks/cane.rake +18 -0
- data/lib/tasks/rpush.rake +16 -0
- data/lib/tasks/test.rake +38 -0
- data/spec/functional/adm_spec.rb +43 -0
- data/spec/functional/apns_spec.rb +58 -0
- data/spec/functional/embed_spec.rb +49 -0
- data/spec/functional/gcm_spec.rb +42 -0
- data/spec/functional/wpns_spec.rb +41 -0
- data/spec/support/cert_with_password.pem +90 -0
- data/spec/support/cert_without_password.pem +59 -0
- data/spec/support/install.sh +32 -0
- data/spec/support/simplecov_helper.rb +20 -0
- data/spec/support/simplecov_quality_formatter.rb +8 -0
- data/spec/tmp/.gitkeep +0 -0
- data/spec/unit/adm/app_spec.rb +58 -0
- data/spec/unit/adm/notification_spec.rb +45 -0
- data/spec/unit/apns/app_spec.rb +29 -0
- data/spec/unit/apns/feedback_spec.rb +9 -0
- data/spec/unit/apns/notification_spec.rb +208 -0
- data/spec/unit/apns_feedback_spec.rb +21 -0
- data/spec/unit/app_spec.rb +30 -0
- data/spec/unit/configuration_spec.rb +45 -0
- data/spec/unit/daemon/adm/delivery_spec.rb +243 -0
- data/spec/unit/daemon/apns/certificate_expired_error_spec.rb +11 -0
- data/spec/unit/daemon/apns/delivery_spec.rb +101 -0
- data/spec/unit/daemon/apns/disconnection_error_spec.rb +18 -0
- data/spec/unit/daemon/apns/feedback_receiver_spec.rb +117 -0
- data/spec/unit/daemon/app_runner_spec.rb +292 -0
- data/spec/unit/daemon/batch_spec.rb +232 -0
- data/spec/unit/daemon/delivery_error_spec.rb +13 -0
- data/spec/unit/daemon/delivery_spec.rb +38 -0
- data/spec/unit/daemon/dispatcher/http_spec.rb +33 -0
- data/spec/unit/daemon/dispatcher/tcp_spec.rb +38 -0
- data/spec/unit/daemon/dispatcher_loop_collection_spec.rb +37 -0
- data/spec/unit/daemon/dispatcher_loop_spec.rb +71 -0
- data/spec/unit/daemon/feeder_spec.rb +98 -0
- data/spec/unit/daemon/gcm/delivery_spec.rb +310 -0
- data/spec/unit/daemon/interruptible_sleep_spec.rb +68 -0
- data/spec/unit/daemon/reflectable_spec.rb +27 -0
- data/spec/unit/daemon/retryable_error_spec.rb +14 -0
- data/spec/unit/daemon/service_config_methods_spec.rb +33 -0
- data/spec/unit/daemon/store/active_record/reconnectable_spec.rb +114 -0
- data/spec/unit/daemon/store/active_record_spec.rb +357 -0
- data/spec/unit/daemon/tcp_connection_spec.rb +287 -0
- data/spec/unit/daemon/too_many_requests_error_spec.rb +14 -0
- data/spec/unit/daemon/wpns/delivery_spec.rb +159 -0
- data/spec/unit/daemon_spec.rb +159 -0
- data/spec/unit/deprecatable_spec.rb +32 -0
- data/spec/unit/deprecation_spec.rb +15 -0
- data/spec/unit/embed_spec.rb +50 -0
- data/spec/unit/gcm/app_spec.rb +4 -0
- data/spec/unit/gcm/notification_spec.rb +36 -0
- data/spec/unit/logger_spec.rb +127 -0
- data/spec/unit/notification_shared.rb +105 -0
- data/spec/unit/notification_spec.rb +15 -0
- data/spec/unit/notifier_spec.rb +49 -0
- data/spec/unit/push_spec.rb +43 -0
- data/spec/unit/reflection_spec.rb +30 -0
- data/spec/unit/rpush_spec.rb +9 -0
- data/spec/unit/wpns/app_spec.rb +4 -0
- data/spec/unit/wpns/notification_spec.rb +30 -0
- data/spec/unit_spec_helper.rb +101 -0
- metadata +304 -0
@@ -0,0 +1,222 @@
|
|
1
|
+
module Rpush
|
2
|
+
module Daemon
|
3
|
+
module Gcm
|
4
|
+
|
5
|
+
# http://developer.android.com/guide/google/gcm/gcm.html#response
|
6
|
+
class Delivery < Rpush::Daemon::Delivery
|
7
|
+
include MultiJsonHelper
|
8
|
+
|
9
|
+
host = ENV["RPUSH_GCM_HOST"] || "https://android.googleapis.com"
|
10
|
+
GCM_URI = URI.parse("#{host}/gcm/send")
|
11
|
+
UNAVAILABLE_STATES = ['Unavailable', 'InternalServerError']
|
12
|
+
INVALID_REGISTRATION_ID_STATES = ['InvalidRegistration', 'MismatchSenderId', 'NotRegistered', 'InvalidPackageName']
|
13
|
+
|
14
|
+
def initialize(app, http, notification, batch)
|
15
|
+
@app = app
|
16
|
+
@http = http
|
17
|
+
@notification = notification
|
18
|
+
@batch = batch
|
19
|
+
end
|
20
|
+
|
21
|
+
def perform
|
22
|
+
begin
|
23
|
+
handle_response(do_post)
|
24
|
+
rescue Rpush::DeliveryError => error
|
25
|
+
mark_failed(error.code, error.description)
|
26
|
+
raise
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
protected
|
31
|
+
|
32
|
+
def handle_response(response)
|
33
|
+
case response.code.to_i
|
34
|
+
when 200
|
35
|
+
ok(response)
|
36
|
+
when 400
|
37
|
+
bad_request
|
38
|
+
when 401
|
39
|
+
unauthorized
|
40
|
+
when 500
|
41
|
+
internal_server_error(response)
|
42
|
+
when 503
|
43
|
+
service_unavailable(response)
|
44
|
+
else
|
45
|
+
raise Rpush::DeliveryError.new(response.code, @notification.id, Rpush::Daemon::HTTP_STATUS_CODES[response.code.to_i])
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def ok(response)
|
50
|
+
results = process_response(response)
|
51
|
+
|
52
|
+
handle_successes(results.successes)
|
53
|
+
|
54
|
+
if results.failures.any?
|
55
|
+
handle_failures(results.failures, response)
|
56
|
+
else
|
57
|
+
mark_delivered
|
58
|
+
log_info("#{@notification.id} sent to #{@notification.registration_ids.join(', ')}")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def process_response(response)
|
63
|
+
body = multi_json_load(response.body)
|
64
|
+
results = Results.new(body['results'], @notification.registration_ids)
|
65
|
+
results.process(invalid: INVALID_REGISTRATION_ID_STATES, unavailable: UNAVAILABLE_STATES)
|
66
|
+
results
|
67
|
+
end
|
68
|
+
|
69
|
+
def handle_successes(successes)
|
70
|
+
successes.each do |result|
|
71
|
+
reflect(:gcm_delivered_to_recipient, @notification, result[:registration_id])
|
72
|
+
if result.has_key?(:canonical_id)
|
73
|
+
reflect(:gcm_canonical_id, result[:registration_id], result[:canonical_id])
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def handle_failures(failures, response)
|
79
|
+
if failures[:unavailable].count == @notification.registration_ids.count
|
80
|
+
retry_delivery(@notification, response)
|
81
|
+
log_warn("All recipients unavailable. #{retry_message}")
|
82
|
+
else
|
83
|
+
if failures[:unavailable].any?
|
84
|
+
unavailable_idxs = failures[:unavailable].map { |result| result[:index] }
|
85
|
+
new_notification = create_new_notification(response, unavailable_idxs)
|
86
|
+
failures.description += " #{unavailable_idxs.join(', ')} will be retried as notification #{new_notification.id}."
|
87
|
+
end
|
88
|
+
handle_errors(failures)
|
89
|
+
raise Rpush::DeliveryError.new(nil, @notification.id, failures.description)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def handle_errors(failures)
|
94
|
+
failures.each do |result|
|
95
|
+
reflect(:gcm_failed_to_recipient, @notification, result[:error], result[:registration_id])
|
96
|
+
end
|
97
|
+
failures[:invalid].each do |result|
|
98
|
+
reflect(:gcm_invalid_registration_id, @app, result[:error], result[:registration_id])
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def create_new_notification(response, unavailable_idxs)
|
103
|
+
attrs = @notification.attributes.slice('app_id', 'collapse_key', 'delay_while_idle')
|
104
|
+
registration_ids = @notification.registration_ids.values_at(*unavailable_idxs)
|
105
|
+
Rpush::Daemon.store.create_gcm_notification(attrs, @notification.data,
|
106
|
+
registration_ids, deliver_after_header(response), @notification.app)
|
107
|
+
end
|
108
|
+
|
109
|
+
def bad_request
|
110
|
+
raise Rpush::DeliveryError.new(400, @notification.id, 'GCM failed to parse the JSON request. Possibly an Rpush bug, please open an issue.')
|
111
|
+
end
|
112
|
+
|
113
|
+
def unauthorized
|
114
|
+
raise Rpush::DeliveryError.new(401, @notification.id, 'Unauthorized, check your App auth_key.')
|
115
|
+
end
|
116
|
+
|
117
|
+
def internal_server_error(response)
|
118
|
+
retry_delivery(@notification, response)
|
119
|
+
log_warn("GCM responded with an Internal Error. " + retry_message)
|
120
|
+
end
|
121
|
+
|
122
|
+
def service_unavailable(response)
|
123
|
+
retry_delivery(@notification, response)
|
124
|
+
log_warn("GCM responded with an Service Unavailable Error. " + retry_message)
|
125
|
+
end
|
126
|
+
|
127
|
+
def deliver_after_header(response)
|
128
|
+
Rpush::Daemon::RetryHeaderParser.parse(response.header['retry-after'])
|
129
|
+
end
|
130
|
+
|
131
|
+
def retry_delivery(notification, response)
|
132
|
+
if time = deliver_after_header(response)
|
133
|
+
mark_retryable(notification, time)
|
134
|
+
else
|
135
|
+
mark_retryable_exponential(notification)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def retry_message
|
140
|
+
"Notification #{@notification.id} will be retried after #{@notification.deliver_after.strftime("%Y-%m-%d %H:%M:%S")} (retry #{@notification.retries})."
|
141
|
+
end
|
142
|
+
|
143
|
+
def do_post
|
144
|
+
post = Net::HTTP::Post.new(GCM_URI.path, initheader = {'Content-Type' => 'application/json',
|
145
|
+
'Authorization' => "key=#{@notification.app.auth_key}"})
|
146
|
+
post.body = @notification.as_json.to_json
|
147
|
+
@http.request(GCM_URI, post)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
class Results
|
152
|
+
attr_reader :successes, :failures
|
153
|
+
|
154
|
+
def initialize(results_data, registration_ids)
|
155
|
+
@results_data = results_data
|
156
|
+
@registration_ids = registration_ids
|
157
|
+
end
|
158
|
+
|
159
|
+
def process(failure_partitions = {})
|
160
|
+
@successes = []
|
161
|
+
@failures = Failures.new
|
162
|
+
failure_partitions.each_key do |category|
|
163
|
+
failures[category] = []
|
164
|
+
end
|
165
|
+
|
166
|
+
@results_data.each_with_index do |result, index|
|
167
|
+
entry = {
|
168
|
+
registration_id: @registration_ids[index],
|
169
|
+
index: index
|
170
|
+
}
|
171
|
+
if result['message_id']
|
172
|
+
entry[:canonical_id] = result['registration_id'] if result['registration_id'].present?
|
173
|
+
successes << entry
|
174
|
+
elsif result['error']
|
175
|
+
entry[:error] = result['error']
|
176
|
+
failures << entry
|
177
|
+
failure_partitions.each do |category, error_states|
|
178
|
+
failures[category] << entry if error_states.include?(result['error'])
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
failures.total_fail = failures.count == @registration_ids.count
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
class Failures < Hash
|
187
|
+
include Enumerable
|
188
|
+
attr_writer :total_fail, :description
|
189
|
+
|
190
|
+
def initialize
|
191
|
+
super[:all] = []
|
192
|
+
end
|
193
|
+
|
194
|
+
def each
|
195
|
+
self[:all].each { |x| yield x }
|
196
|
+
end
|
197
|
+
|
198
|
+
def <<(item)
|
199
|
+
self[:all] << item
|
200
|
+
end
|
201
|
+
|
202
|
+
def description
|
203
|
+
@description ||= describe
|
204
|
+
end
|
205
|
+
|
206
|
+
private
|
207
|
+
|
208
|
+
def describe
|
209
|
+
if @total_fail
|
210
|
+
error_description = "Failed to deliver to all recipients."
|
211
|
+
else
|
212
|
+
index_list = map { |item| item[:index] }
|
213
|
+
error_description = "Failed to deliver to recipients #{index_list.join(', ')}."
|
214
|
+
end
|
215
|
+
|
216
|
+
error_list = map { |item| item[:error] }
|
217
|
+
error_description + " Errors: #{error_list.join(', ')}."
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Rpush
|
2
|
+
module Daemon
|
3
|
+
class InterruptibleSleep
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@sleep_reader, @wake_writer = IO.pipe
|
7
|
+
@udp_wakeup = nil
|
8
|
+
end
|
9
|
+
|
10
|
+
# enable wake on receiving udp packets at the given address and port
|
11
|
+
# this returns the host,port used by bind in case an ephemeral port
|
12
|
+
# was indicated by specifying 0 as the port number.
|
13
|
+
# @return [String,Integer] host,port of bound UDP socket.
|
14
|
+
def enable_wake_on_udp(host, port)
|
15
|
+
@udp_wakeup = UDPSocket.new
|
16
|
+
@udp_wakeup.bind(host, port)
|
17
|
+
@udp_wakeup.addr.values_at(3,1)
|
18
|
+
end
|
19
|
+
|
20
|
+
# wait for the given timeout in seconds, or data was written to the pipe
|
21
|
+
# or the udp wakeup port if enabled.
|
22
|
+
# @return [boolean] true if the sleep was interrupted, or false
|
23
|
+
def sleep(timeout)
|
24
|
+
read_ports = [@sleep_reader]
|
25
|
+
read_ports << @udp_wakeup if @udp_wakeup
|
26
|
+
rs, = IO.select(read_ports, nil, nil, timeout) rescue nil
|
27
|
+
|
28
|
+
# consume all data on the readable io's so that our next call will wait for more data
|
29
|
+
perform_io(rs, @sleep_reader, :read_nonblock)
|
30
|
+
perform_io(rs, @udp_wakeup, :recv_nonblock)
|
31
|
+
|
32
|
+
!rs.nil? && rs.any?
|
33
|
+
end
|
34
|
+
|
35
|
+
# writing to the pipe will wake the sleeping thread
|
36
|
+
def interrupt_sleep
|
37
|
+
@wake_writer.write('.')
|
38
|
+
end
|
39
|
+
|
40
|
+
def close
|
41
|
+
@sleep_reader.close rescue nil
|
42
|
+
@wake_writer.close rescue nil
|
43
|
+
@udp_wakeup.close if @udp_wakeup rescue nil
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def perform_io(selected, io, meth)
|
49
|
+
if selected && selected.include?(io)
|
50
|
+
while true
|
51
|
+
begin
|
52
|
+
io.__send__(meth, 1)
|
53
|
+
rescue Errno::EAGAIN, IO::WaitReadable
|
54
|
+
break
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Rpush
|
2
|
+
module Daemon
|
3
|
+
module Loggable
|
4
|
+
def log_info(msg)
|
5
|
+
Rpush.logger.info(app_prefix(msg))
|
6
|
+
end
|
7
|
+
|
8
|
+
def log_warn(msg)
|
9
|
+
Rpush.logger.warn(app_prefix(msg))
|
10
|
+
end
|
11
|
+
|
12
|
+
def log_error(e)
|
13
|
+
if e.is_a?(Exception)
|
14
|
+
Rpush.logger.error(e)
|
15
|
+
else
|
16
|
+
Rpush.logger.error(app_prefix(e))
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def app_prefix(msg)
|
23
|
+
if app = instance_variable_get('@app')
|
24
|
+
msg = "[#{app.name}] #{msg}"
|
25
|
+
end
|
26
|
+
|
27
|
+
msg
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Rpush
|
2
|
+
module Daemon
|
3
|
+
class RetryHeaderParser
|
4
|
+
def self.parse(header)
|
5
|
+
new(header).parse
|
6
|
+
end
|
7
|
+
|
8
|
+
def initialize(header)
|
9
|
+
@header = header
|
10
|
+
end
|
11
|
+
|
12
|
+
def parse
|
13
|
+
if @header
|
14
|
+
if @header.to_s =~ /^[0-9]+$/
|
15
|
+
Time.now + @header.to_i
|
16
|
+
else
|
17
|
+
Time.httpdate(@header)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Rpush
|
2
|
+
class RetryableError < StandardError
|
3
|
+
attr_reader :code, :description, :response
|
4
|
+
|
5
|
+
def initialize(code, notification_id, description, response)
|
6
|
+
@code = code
|
7
|
+
@notification_id = notification_id
|
8
|
+
@description = description
|
9
|
+
@response = response
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_s
|
13
|
+
message
|
14
|
+
end
|
15
|
+
|
16
|
+
def message
|
17
|
+
"Retryable error for #{@notification_id}, received error #{@code} (#{@description}) - retry after #{@response.header['retry-after']}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Rpush
|
2
|
+
module Daemon
|
3
|
+
module ServiceConfigMethods
|
4
|
+
DISPATCHERS = {
|
5
|
+
:http => Rpush::Daemon::Dispatcher::Http,
|
6
|
+
:tcp => Rpush::Daemon::Dispatcher::Tcp
|
7
|
+
}
|
8
|
+
|
9
|
+
def dispatcher(name = nil, options = {})
|
10
|
+
@dispatcher_name = name
|
11
|
+
@dispatcher_options = options
|
12
|
+
end
|
13
|
+
|
14
|
+
def dispatcher_class
|
15
|
+
DISPATCHERS[@dispatcher_name] || (raise NotImplementedError)
|
16
|
+
end
|
17
|
+
|
18
|
+
def delivery_class
|
19
|
+
const_get('Delivery')
|
20
|
+
end
|
21
|
+
|
22
|
+
def new_dispatcher(app)
|
23
|
+
dispatcher_class.new(app, delivery_class, @dispatcher_options)
|
24
|
+
end
|
25
|
+
|
26
|
+
def loops(*loops)
|
27
|
+
@loops ||= []
|
28
|
+
@loops = loops if loops.any?
|
29
|
+
@loops
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,68 @@
|
|
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
|
+
|
7
|
+
# :nocov:
|
8
|
+
if !defined?(::SQLite3::Exception)
|
9
|
+
module SQLite3
|
10
|
+
class Exception < StandardError; end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module Rpush
|
15
|
+
module Daemon
|
16
|
+
module Store
|
17
|
+
class ActiveRecord
|
18
|
+
module Reconnectable
|
19
|
+
ADAPTER_ERRORS = [::ActiveRecord::StatementInvalid, PGError, Mysql::Error,
|
20
|
+
Mysql2::Error, ::ActiveRecord::JDBCError, SQLite3::Exception]
|
21
|
+
|
22
|
+
def with_database_reconnect_and_retry
|
23
|
+
begin
|
24
|
+
::ActiveRecord::Base.connection_pool.with_connection do
|
25
|
+
yield
|
26
|
+
end
|
27
|
+
rescue *ADAPTER_ERRORS => e
|
28
|
+
Rpush.logger.error(e)
|
29
|
+
database_connection_lost
|
30
|
+
retry
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def database_connection_lost
|
35
|
+
Rpush.logger.warn("Lost connection to database, reconnecting...")
|
36
|
+
attempts = 0
|
37
|
+
loop do
|
38
|
+
begin
|
39
|
+
Rpush.logger.warn("Attempt #{attempts += 1}")
|
40
|
+
reconnect_database
|
41
|
+
check_database_is_connected
|
42
|
+
break
|
43
|
+
rescue *ADAPTER_ERRORS => e
|
44
|
+
Rpush.logger.error(e)
|
45
|
+
sleep_to_avoid_thrashing
|
46
|
+
end
|
47
|
+
end
|
48
|
+
Rpush.logger.warn("Database reconnected")
|
49
|
+
end
|
50
|
+
|
51
|
+
def reconnect_database
|
52
|
+
::ActiveRecord::Base.clear_all_connections!
|
53
|
+
::ActiveRecord::Base.establish_connection
|
54
|
+
end
|
55
|
+
|
56
|
+
def check_database_is_connected
|
57
|
+
# Simply asking the adapter for the connection state is not sufficient.
|
58
|
+
Rpush::Notification.count
|
59
|
+
end
|
60
|
+
|
61
|
+
def sleep_to_avoid_thrashing
|
62
|
+
sleep 2
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
require 'rpush/daemon/store/active_record/reconnectable'
|
4
|
+
|
5
|
+
module Rpush
|
6
|
+
module Daemon
|
7
|
+
module Store
|
8
|
+
class ActiveRecord
|
9
|
+
include Reconnectable
|
10
|
+
|
11
|
+
DEFAULT_MARK_OPTIONS = {:persist => true}
|
12
|
+
|
13
|
+
def deliverable_notifications(apps)
|
14
|
+
with_database_reconnect_and_retry do
|
15
|
+
batch_size = Rpush.config.batch_size
|
16
|
+
relation = Rpush::Notification.ready_for_delivery.for_apps(apps)
|
17
|
+
relation = relation.limit(batch_size) unless Rpush.config.push
|
18
|
+
relation.to_a
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def mark_retryable(notification, deliver_after, opts = {})
|
23
|
+
opts = DEFAULT_MARK_OPTIONS.dup.merge(opts)
|
24
|
+
notification.retries += 1
|
25
|
+
notification.deliver_after = deliver_after
|
26
|
+
|
27
|
+
if opts[:persist]
|
28
|
+
with_database_reconnect_and_retry do
|
29
|
+
notification.save!(:validate => false)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def mark_batch_retryable(notifications, deliver_after)
|
35
|
+
ids = []
|
36
|
+
notifications.each do |n|
|
37
|
+
mark_retryable(n, deliver_after, :persist => false)
|
38
|
+
ids << n.id
|
39
|
+
end
|
40
|
+
with_database_reconnect_and_retry do
|
41
|
+
Rpush::Notification.where(:id => ids).update_all(['retries = retries + 1, deliver_after = ?', deliver_after])
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def mark_delivered(notification, time, opts = {})
|
46
|
+
opts = DEFAULT_MARK_OPTIONS.dup.merge(opts)
|
47
|
+
notification.delivered = true
|
48
|
+
notification.delivered_at = time
|
49
|
+
|
50
|
+
if opts[:persist]
|
51
|
+
with_database_reconnect_and_retry do
|
52
|
+
notification.save!(:validate => false)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def mark_batch_delivered(notifications)
|
58
|
+
now = Time.now
|
59
|
+
ids = []
|
60
|
+
notifications.each do |n|
|
61
|
+
mark_delivered(n, now, :persist => false)
|
62
|
+
ids << n.id
|
63
|
+
end
|
64
|
+
with_database_reconnect_and_retry do
|
65
|
+
Rpush::Notification.where(:id => ids).update_all(['delivered = ?, delivered_at = ?', true, now])
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def mark_failed(notification, code, description, time, opts = {})
|
70
|
+
opts = DEFAULT_MARK_OPTIONS.dup.merge(opts)
|
71
|
+
notification.delivered = false
|
72
|
+
notification.delivered_at = nil
|
73
|
+
notification.failed = true
|
74
|
+
notification.failed_at = time
|
75
|
+
notification.error_code = code
|
76
|
+
notification.error_description = description
|
77
|
+
|
78
|
+
if opts[:persist]
|
79
|
+
with_database_reconnect_and_retry do
|
80
|
+
notification.save!(:validate => false)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def mark_batch_failed(notifications, code, description)
|
86
|
+
now = Time.now
|
87
|
+
ids = []
|
88
|
+
notifications.each do |n|
|
89
|
+
mark_failed(n, code, description, now, :persist => false)
|
90
|
+
ids << n.id
|
91
|
+
end
|
92
|
+
with_database_reconnect_and_retry do
|
93
|
+
Rpush::Notification.where(:id => ids).update_all(['delivered = ?, delivered_at = NULL, failed = ?, failed_at = ?, error_code = ?, error_description = ?', false, true, now, code, description])
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def create_apns_feedback(failed_at, device_token, app)
|
98
|
+
with_database_reconnect_and_retry do
|
99
|
+
Rpush::Apns::Feedback.create!(:failed_at => failed_at,
|
100
|
+
:device_token => device_token, :app => app)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def create_gcm_notification(attrs, data, registration_ids, deliver_after, app)
|
105
|
+
notification = Rpush::Gcm::Notification.new
|
106
|
+
create_gcm_like_notification(notification, attrs, data, registration_ids, deliver_after, app)
|
107
|
+
end
|
108
|
+
|
109
|
+
def create_adm_notification(attrs, data, registration_ids, deliver_after, app)
|
110
|
+
notification = Rpush::Adm::Notification.new
|
111
|
+
create_gcm_like_notification(notification, attrs, data, registration_ids, deliver_after, app)
|
112
|
+
end
|
113
|
+
|
114
|
+
def update_app(app)
|
115
|
+
with_database_reconnect_and_retry do
|
116
|
+
app.save!
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def update_notification(notification)
|
121
|
+
with_database_reconnect_and_retry do
|
122
|
+
notification.save!
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def after_daemonize
|
127
|
+
reconnect_database
|
128
|
+
end
|
129
|
+
|
130
|
+
def release_connection
|
131
|
+
begin
|
132
|
+
::ActiveRecord::Base.connection_pool.release_connection
|
133
|
+
rescue StandardError => e
|
134
|
+
Rpush.logger.error(e)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
private
|
139
|
+
|
140
|
+
def create_gcm_like_notification(notification, attrs, data, registration_ids, deliver_after, app)
|
141
|
+
with_database_reconnect_and_retry do
|
142
|
+
notification.assign_attributes(attrs)
|
143
|
+
notification.data = data
|
144
|
+
notification.registration_ids = registration_ids
|
145
|
+
notification.deliver_after = deliver_after
|
146
|
+
notification.app = app
|
147
|
+
notification.save!
|
148
|
+
notification
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|