rpush 1.0.0

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 (145) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +99 -0
  3. data/LICENSE +7 -0
  4. data/README.md +189 -0
  5. data/bin/rpush +36 -0
  6. data/config/database.yml +44 -0
  7. data/lib/generators/rpush_generator.rb +44 -0
  8. data/lib/generators/templates/add_adm.rb +23 -0
  9. data/lib/generators/templates/add_alert_is_json_to_rapns_notifications.rb +9 -0
  10. data/lib/generators/templates/add_app_to_rapns.rb +11 -0
  11. data/lib/generators/templates/add_fail_after_to_rpush_notifications.rb +9 -0
  12. data/lib/generators/templates/add_gcm.rb +102 -0
  13. data/lib/generators/templates/add_rpush.rb +349 -0
  14. data/lib/generators/templates/add_wpns.rb +16 -0
  15. data/lib/generators/templates/create_rapns_apps.rb +16 -0
  16. data/lib/generators/templates/create_rapns_feedback.rb +18 -0
  17. data/lib/generators/templates/create_rapns_notifications.rb +29 -0
  18. data/lib/generators/templates/rename_rapns_to_rpush.rb +63 -0
  19. data/lib/generators/templates/rpush.rb +104 -0
  20. data/lib/rpush.rb +62 -0
  21. data/lib/rpush/TODO +3 -0
  22. data/lib/rpush/adm/app.rb +15 -0
  23. data/lib/rpush/adm/data_validator.rb +11 -0
  24. data/lib/rpush/adm/notification.rb +29 -0
  25. data/lib/rpush/apns/app.rb +29 -0
  26. data/lib/rpush/apns/binary_notification_validator.rb +12 -0
  27. data/lib/rpush/apns/device_token_format_validator.rb +12 -0
  28. data/lib/rpush/apns/feedback.rb +16 -0
  29. data/lib/rpush/apns/notification.rb +84 -0
  30. data/lib/rpush/apns_feedback.rb +13 -0
  31. data/lib/rpush/app.rb +18 -0
  32. data/lib/rpush/configuration.rb +75 -0
  33. data/lib/rpush/daemon.rb +140 -0
  34. data/lib/rpush/daemon/adm.rb +9 -0
  35. data/lib/rpush/daemon/adm/delivery.rb +222 -0
  36. data/lib/rpush/daemon/apns.rb +16 -0
  37. data/lib/rpush/daemon/apns/certificate_expired_error.rb +20 -0
  38. data/lib/rpush/daemon/apns/delivery.rb +64 -0
  39. data/lib/rpush/daemon/apns/disconnection_error.rb +20 -0
  40. data/lib/rpush/daemon/apns/feedback_receiver.rb +79 -0
  41. data/lib/rpush/daemon/app_runner.rb +187 -0
  42. data/lib/rpush/daemon/batch.rb +115 -0
  43. data/lib/rpush/daemon/constants.rb +59 -0
  44. data/lib/rpush/daemon/delivery.rb +28 -0
  45. data/lib/rpush/daemon/delivery_error.rb +19 -0
  46. data/lib/rpush/daemon/dispatcher/http.rb +21 -0
  47. data/lib/rpush/daemon/dispatcher/tcp.rb +30 -0
  48. data/lib/rpush/daemon/dispatcher_loop.rb +54 -0
  49. data/lib/rpush/daemon/dispatcher_loop_collection.rb +33 -0
  50. data/lib/rpush/daemon/feeder.rb +68 -0
  51. data/lib/rpush/daemon/gcm.rb +9 -0
  52. data/lib/rpush/daemon/gcm/delivery.rb +222 -0
  53. data/lib/rpush/daemon/interruptible_sleep.rb +61 -0
  54. data/lib/rpush/daemon/loggable.rb +31 -0
  55. data/lib/rpush/daemon/reflectable.rb +13 -0
  56. data/lib/rpush/daemon/retry_header_parser.rb +23 -0
  57. data/lib/rpush/daemon/retryable_error.rb +20 -0
  58. data/lib/rpush/daemon/service_config_methods.rb +33 -0
  59. data/lib/rpush/daemon/store/active_record.rb +154 -0
  60. data/lib/rpush/daemon/store/active_record/reconnectable.rb +68 -0
  61. data/lib/rpush/daemon/tcp_connection.rb +143 -0
  62. data/lib/rpush/daemon/too_many_requests_error.rb +20 -0
  63. data/lib/rpush/daemon/wpns.rb +9 -0
  64. data/lib/rpush/daemon/wpns/delivery.rb +132 -0
  65. data/lib/rpush/deprecatable.rb +23 -0
  66. data/lib/rpush/deprecation.rb +23 -0
  67. data/lib/rpush/embed.rb +28 -0
  68. data/lib/rpush/gcm/app.rb +11 -0
  69. data/lib/rpush/gcm/expiry_collapse_key_mutual_inclusion_validator.rb +11 -0
  70. data/lib/rpush/gcm/notification.rb +30 -0
  71. data/lib/rpush/logger.rb +63 -0
  72. data/lib/rpush/multi_json_helper.rb +16 -0
  73. data/lib/rpush/notification.rb +69 -0
  74. data/lib/rpush/notifier.rb +52 -0
  75. data/lib/rpush/payload_data_size_validator.rb +10 -0
  76. data/lib/rpush/push.rb +16 -0
  77. data/lib/rpush/railtie.rb +11 -0
  78. data/lib/rpush/reflection.rb +58 -0
  79. data/lib/rpush/registration_ids_count_validator.rb +10 -0
  80. data/lib/rpush/version.rb +3 -0
  81. data/lib/rpush/wpns/app.rb +9 -0
  82. data/lib/rpush/wpns/notification.rb +26 -0
  83. data/lib/tasks/cane.rake +18 -0
  84. data/lib/tasks/rpush.rake +16 -0
  85. data/lib/tasks/test.rake +38 -0
  86. data/spec/functional/adm_spec.rb +43 -0
  87. data/spec/functional/apns_spec.rb +58 -0
  88. data/spec/functional/embed_spec.rb +49 -0
  89. data/spec/functional/gcm_spec.rb +42 -0
  90. data/spec/functional/wpns_spec.rb +41 -0
  91. data/spec/support/cert_with_password.pem +90 -0
  92. data/spec/support/cert_without_password.pem +59 -0
  93. data/spec/support/install.sh +32 -0
  94. data/spec/support/simplecov_helper.rb +20 -0
  95. data/spec/support/simplecov_quality_formatter.rb +8 -0
  96. data/spec/tmp/.gitkeep +0 -0
  97. data/spec/unit/adm/app_spec.rb +58 -0
  98. data/spec/unit/adm/notification_spec.rb +45 -0
  99. data/spec/unit/apns/app_spec.rb +29 -0
  100. data/spec/unit/apns/feedback_spec.rb +9 -0
  101. data/spec/unit/apns/notification_spec.rb +208 -0
  102. data/spec/unit/apns_feedback_spec.rb +21 -0
  103. data/spec/unit/app_spec.rb +30 -0
  104. data/spec/unit/configuration_spec.rb +45 -0
  105. data/spec/unit/daemon/adm/delivery_spec.rb +243 -0
  106. data/spec/unit/daemon/apns/certificate_expired_error_spec.rb +11 -0
  107. data/spec/unit/daemon/apns/delivery_spec.rb +101 -0
  108. data/spec/unit/daemon/apns/disconnection_error_spec.rb +18 -0
  109. data/spec/unit/daemon/apns/feedback_receiver_spec.rb +117 -0
  110. data/spec/unit/daemon/app_runner_spec.rb +292 -0
  111. data/spec/unit/daemon/batch_spec.rb +232 -0
  112. data/spec/unit/daemon/delivery_error_spec.rb +13 -0
  113. data/spec/unit/daemon/delivery_spec.rb +38 -0
  114. data/spec/unit/daemon/dispatcher/http_spec.rb +33 -0
  115. data/spec/unit/daemon/dispatcher/tcp_spec.rb +38 -0
  116. data/spec/unit/daemon/dispatcher_loop_collection_spec.rb +37 -0
  117. data/spec/unit/daemon/dispatcher_loop_spec.rb +71 -0
  118. data/spec/unit/daemon/feeder_spec.rb +98 -0
  119. data/spec/unit/daemon/gcm/delivery_spec.rb +310 -0
  120. data/spec/unit/daemon/interruptible_sleep_spec.rb +68 -0
  121. data/spec/unit/daemon/reflectable_spec.rb +27 -0
  122. data/spec/unit/daemon/retryable_error_spec.rb +14 -0
  123. data/spec/unit/daemon/service_config_methods_spec.rb +33 -0
  124. data/spec/unit/daemon/store/active_record/reconnectable_spec.rb +114 -0
  125. data/spec/unit/daemon/store/active_record_spec.rb +357 -0
  126. data/spec/unit/daemon/tcp_connection_spec.rb +287 -0
  127. data/spec/unit/daemon/too_many_requests_error_spec.rb +14 -0
  128. data/spec/unit/daemon/wpns/delivery_spec.rb +159 -0
  129. data/spec/unit/daemon_spec.rb +159 -0
  130. data/spec/unit/deprecatable_spec.rb +32 -0
  131. data/spec/unit/deprecation_spec.rb +15 -0
  132. data/spec/unit/embed_spec.rb +50 -0
  133. data/spec/unit/gcm/app_spec.rb +4 -0
  134. data/spec/unit/gcm/notification_spec.rb +36 -0
  135. data/spec/unit/logger_spec.rb +127 -0
  136. data/spec/unit/notification_shared.rb +105 -0
  137. data/spec/unit/notification_spec.rb +15 -0
  138. data/spec/unit/notifier_spec.rb +49 -0
  139. data/spec/unit/push_spec.rb +43 -0
  140. data/spec/unit/reflection_spec.rb +30 -0
  141. data/spec/unit/rpush_spec.rb +9 -0
  142. data/spec/unit/wpns/app_spec.rb +4 -0
  143. data/spec/unit/wpns/notification_spec.rb +30 -0
  144. data/spec/unit_spec_helper.rb +101 -0
  145. metadata +276 -0
@@ -0,0 +1,59 @@
1
+ module Rpush
2
+ module Daemon
3
+ HTTP_STATUS_CODES = {
4
+ 100 => 'Continue',
5
+ 101 => 'Switching Protocols',
6
+ 102 => 'Processing',
7
+ 200 => 'OK',
8
+ 201 => 'Created',
9
+ 202 => 'Accepted',
10
+ 203 => 'Non-Authoritative Information',
11
+ 204 => 'No Content',
12
+ 205 => 'Reset Content',
13
+ 206 => 'Partial Content',
14
+ 207 => 'Multi-Status',
15
+ 226 => 'IM Used',
16
+ 300 => 'Multiple Choices',
17
+ 301 => 'Moved Permanently',
18
+ 302 => 'Found',
19
+ 303 => 'See Other',
20
+ 304 => 'Not Modified',
21
+ 305 => 'Use Proxy',
22
+ 306 => 'Reserved',
23
+ 307 => 'Temporary Redirect',
24
+ 400 => 'Bad Request',
25
+ 401 => 'Unauthorized',
26
+ 402 => 'Payment Required',
27
+ 403 => 'Forbidden',
28
+ 404 => 'Not Found',
29
+ 405 => 'Method Not Allowed',
30
+ 406 => 'Not Acceptable',
31
+ 407 => 'Proxy Authentication Required',
32
+ 408 => 'Request Timeout',
33
+ 409 => 'Conflict',
34
+ 410 => 'Gone',
35
+ 411 => 'Length Required',
36
+ 412 => 'Precondition Failed',
37
+ 413 => 'Request Entity Too Large',
38
+ 414 => 'Request-URI Too Long',
39
+ 415 => 'Unsupported Media Type',
40
+ 416 => 'Requested Range Not Satisfiable',
41
+ 417 => 'Expectation Failed',
42
+ 418 => "I'm a Teapot",
43
+ 422 => 'Unprocessable Entity',
44
+ 423 => 'Locked',
45
+ 424 => 'Failed Dependency',
46
+ 426 => 'Upgrade Required',
47
+ 429 => 'Too Many Requests',
48
+ 500 => 'Internal Server Error',
49
+ 501 => 'Not Implemented',
50
+ 502 => 'Bad Gateway',
51
+ 503 => 'Service Unavailable',
52
+ 504 => 'Gateway Timeout',
53
+ 505 => 'HTTP Version Not Supported',
54
+ 506 => 'Variant Also Negotiates',
55
+ 507 => 'Insufficient Storage',
56
+ 510 => 'Not Extended'
57
+ }
58
+ end
59
+ end
@@ -0,0 +1,28 @@
1
+ module Rpush
2
+ module Daemon
3
+ class Delivery
4
+ include Reflectable
5
+ include Loggable
6
+
7
+ def mark_retryable(notification, deliver_after)
8
+ if notification.fail_after && notification.fail_after < Time.now
9
+ @batch.mark_failed(notification, nil, "Notification failed to be delivered before #{notification.fail_after.strftime("%Y-%m-%d %H:%M:%S")}.")
10
+ else
11
+ @batch.mark_retryable(notification, deliver_after)
12
+ end
13
+ end
14
+
15
+ def mark_retryable_exponential(notification)
16
+ mark_retryable(notification, Time.now + 2 ** (notification.retries + 1))
17
+ end
18
+
19
+ def mark_delivered
20
+ @batch.mark_delivered(@notification)
21
+ end
22
+
23
+ def mark_failed(code, description)
24
+ @batch.mark_failed(@notification, code, description)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,19 @@
1
+ module Rpush
2
+ class DeliveryError < StandardError
3
+ attr_reader :code, :description
4
+
5
+ def initialize(code, notification_id, description)
6
+ @code = code
7
+ @notification_id = notification_id
8
+ @description = description
9
+ end
10
+
11
+ def to_s
12
+ message
13
+ end
14
+
15
+ def message
16
+ "Unable to deliver notification #{@notification_id}, received error #{@code} (#{@description})"
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,21 @@
1
+ module Rpush
2
+ module Daemon
3
+ module Dispatcher
4
+ class Http
5
+ def initialize(app, delivery_class, options = {})
6
+ @app = app
7
+ @delivery_class = delivery_class
8
+ @http = Net::HTTP::Persistent.new('rpush')
9
+ end
10
+
11
+ def dispatch(notification, batch)
12
+ @delivery_class.new(@app, @http, notification, batch).perform
13
+ end
14
+
15
+ def cleanup
16
+ @http.shutdown
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,30 @@
1
+ module Rpush
2
+ module Daemon
3
+ module Dispatcher
4
+ class Tcp
5
+ def initialize(app, delivery_class, options = {})
6
+ @app = app
7
+ @delivery_class = delivery_class
8
+ @host, @port = options[:host].call(@app)
9
+ end
10
+
11
+ def dispatch(notification, batch)
12
+ @delivery_class.new(@app, connection, notification, batch).perform
13
+ end
14
+
15
+ def cleanup
16
+ @connection.close if @connection
17
+ end
18
+
19
+ private
20
+
21
+ def connection
22
+ return @connection if defined? @connection
23
+ connection = Rpush::Daemon::TcpConnection.new(@app, @host, @port)
24
+ connection.connect
25
+ @connection = connection
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,54 @@
1
+ module Rpush
2
+ module Daemon
3
+ class DispatcherLoop
4
+ include Reflectable
5
+
6
+ WAKEUP = :wakeup
7
+
8
+ def initialize(queue, dispatcher)
9
+ @queue = queue
10
+ @dispatcher = dispatcher
11
+ end
12
+
13
+ def start
14
+ @thread = Thread.new do
15
+ loop do
16
+ dispatch
17
+ break if @stop
18
+ end
19
+
20
+ Rpush::Daemon.store.release_connection
21
+ end
22
+ end
23
+
24
+ def stop
25
+ @stop = true
26
+ end
27
+
28
+ def wakeup
29
+ @queue.push(WAKEUP) if @thread
30
+ end
31
+
32
+ def wait
33
+ @thread.join if @thread
34
+ @dispatcher.cleanup
35
+ end
36
+
37
+ protected
38
+
39
+ def dispatch
40
+ notification, batch = @queue.pop
41
+ return if notification == WAKEUP
42
+
43
+ begin
44
+ @dispatcher.dispatch(notification, batch)
45
+ rescue StandardError => e
46
+ Rpush.logger.error(e)
47
+ reflect(:error, e)
48
+ ensure
49
+ batch.notification_dispatched
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,33 @@
1
+ module Rpush
2
+ module Daemon
3
+ class DispatcherLoopCollection
4
+ attr_reader :loops
5
+
6
+ def initialize
7
+ @loops = []
8
+ end
9
+
10
+ def push(dispatcher_loop)
11
+ @loops << dispatcher_loop
12
+ end
13
+
14
+ def pop
15
+ dispatcher_loop = @loops.pop
16
+ dispatcher_loop.stop
17
+ dispatcher_loop.wakeup
18
+ @loops.map(&:wakeup)
19
+ dispatcher_loop.wait
20
+ end
21
+
22
+ def size
23
+ @loops.size
24
+ end
25
+
26
+ def stop
27
+ @loops.map(&:stop)
28
+ @loops.map(&:wakeup)
29
+ @loops.map(&:wait)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,68 @@
1
+ module Rpush
2
+ module Daemon
3
+ class Feeder
4
+ extend Reflectable
5
+
6
+ def self.start
7
+ @stop = false
8
+
9
+ if Rpush.config.embedded
10
+ Thread.new { feed_forever }
11
+ elsif Rpush.config.push
12
+ enqueue_notifications
13
+ else
14
+ feed_forever
15
+ end
16
+ end
17
+
18
+ def self.stop
19
+ @stop = true
20
+ interrupt_sleep
21
+ end
22
+
23
+ def self.interrupt_sleep
24
+ interruptible_sleeper.interrupt_sleep
25
+ end
26
+
27
+ protected
28
+
29
+ def self.feed_forever
30
+ loop do
31
+ enqueue_notifications
32
+ interruptible_sleeper.sleep(Rpush.config.push_poll)
33
+ break if stop?
34
+ end
35
+
36
+ Rpush::Daemon.store.release_connection
37
+ end
38
+
39
+ # :nocov:
40
+ def self.stop?
41
+ @stop
42
+ end
43
+
44
+ def self.enqueue_notifications
45
+ begin
46
+ idle = Rpush::Daemon::AppRunner.idle.map(&:app)
47
+ return if idle.empty?
48
+ notifications = Rpush::Daemon.store.deliverable_notifications(idle)
49
+ Rpush::Daemon::AppRunner.enqueue(notifications)
50
+ rescue StandardError => e
51
+ Rpush.logger.error(e)
52
+ reflect(:error, e)
53
+ end
54
+ end
55
+
56
+ def self.interruptible_sleeper
57
+ return @interruptible_sleeper if @interruptible_sleeper
58
+
59
+ @interruptible_sleeper = InterruptibleSleep.new
60
+ if Rpush.config.wakeup
61
+ @interruptible_sleeper.enable_wake_on_udp Rpush.config.wakeup[:bind], Rpush.config.wakeup[:port]
62
+ end
63
+
64
+ @interruptible_sleeper
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,9 @@
1
+ module Rpush
2
+ module Daemon
3
+ module Gcm
4
+ extend ServiceConfigMethods
5
+
6
+ dispatcher :http
7
+ end
8
+ end
9
+ end
@@ -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