rapns 3.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.
Files changed (89) hide show
  1. data/CHANGELOG.md +31 -0
  2. data/LICENSE +7 -0
  3. data/README.md +138 -0
  4. data/bin/rapns +41 -0
  5. data/config/database.yml +39 -0
  6. data/lib/generators/rapns_generator.rb +34 -0
  7. data/lib/generators/templates/add_alert_is_json_to_rapns_notifications.rb +9 -0
  8. data/lib/generators/templates/add_app_to_rapns.rb +11 -0
  9. data/lib/generators/templates/add_gcm.rb +94 -0
  10. data/lib/generators/templates/create_rapns_apps.rb +16 -0
  11. data/lib/generators/templates/create_rapns_feedback.rb +15 -0
  12. data/lib/generators/templates/create_rapns_notifications.rb +26 -0
  13. data/lib/generators/templates/rapns.rb +39 -0
  14. data/lib/rapns/apns/app.rb +8 -0
  15. data/lib/rapns/apns/binary_notification_validator.rb +12 -0
  16. data/lib/rapns/apns/device_token_format_validator.rb +12 -0
  17. data/lib/rapns/apns/feedback.rb +14 -0
  18. data/lib/rapns/apns/notification.rb +86 -0
  19. data/lib/rapns/apns/required_fields_validator.rb +14 -0
  20. data/lib/rapns/app.rb +29 -0
  21. data/lib/rapns/configuration.rb +46 -0
  22. data/lib/rapns/daemon/apns/app_runner.rb +36 -0
  23. data/lib/rapns/daemon/apns/connection.rb +113 -0
  24. data/lib/rapns/daemon/apns/delivery.rb +63 -0
  25. data/lib/rapns/daemon/apns/delivery_handler.rb +21 -0
  26. data/lib/rapns/daemon/apns/disconnection_error.rb +20 -0
  27. data/lib/rapns/daemon/apns/feedback_receiver.rb +74 -0
  28. data/lib/rapns/daemon/app_runner.rb +135 -0
  29. data/lib/rapns/daemon/database_reconnectable.rb +57 -0
  30. data/lib/rapns/daemon/delivery.rb +43 -0
  31. data/lib/rapns/daemon/delivery_error.rb +19 -0
  32. data/lib/rapns/daemon/delivery_handler.rb +46 -0
  33. data/lib/rapns/daemon/delivery_queue.rb +42 -0
  34. data/lib/rapns/daemon/delivery_queue_18.rb +44 -0
  35. data/lib/rapns/daemon/delivery_queue_19.rb +42 -0
  36. data/lib/rapns/daemon/feeder.rb +37 -0
  37. data/lib/rapns/daemon/gcm/app_runner.rb +13 -0
  38. data/lib/rapns/daemon/gcm/delivery.rb +206 -0
  39. data/lib/rapns/daemon/gcm/delivery_handler.rb +20 -0
  40. data/lib/rapns/daemon/interruptible_sleep.rb +18 -0
  41. data/lib/rapns/daemon/logger.rb +68 -0
  42. data/lib/rapns/daemon.rb +136 -0
  43. data/lib/rapns/gcm/app.rb +7 -0
  44. data/lib/rapns/gcm/expiry_collapse_key_mutual_inclusion_validator.rb +11 -0
  45. data/lib/rapns/gcm/notification.rb +31 -0
  46. data/lib/rapns/gcm/payload_size_validator.rb +13 -0
  47. data/lib/rapns/multi_json_helper.rb +16 -0
  48. data/lib/rapns/notification.rb +54 -0
  49. data/lib/rapns/patches/rails/3.1.0/postgresql_adapter.rb +12 -0
  50. data/lib/rapns/patches/rails/3.1.1/postgresql_adapter.rb +17 -0
  51. data/lib/rapns/patches.rb +6 -0
  52. data/lib/rapns/version.rb +3 -0
  53. data/lib/rapns.rb +21 -0
  54. data/lib/tasks/cane.rake +18 -0
  55. data/lib/tasks/test.rake +33 -0
  56. data/spec/acceptance/gcm_upgrade_spec.rb +34 -0
  57. data/spec/acceptance_spec_helper.rb +85 -0
  58. data/spec/support/simplecov_helper.rb +13 -0
  59. data/spec/support/simplecov_quality_formatter.rb +8 -0
  60. data/spec/unit/apns/app_spec.rb +15 -0
  61. data/spec/unit/apns/feedback_spec.rb +12 -0
  62. data/spec/unit/apns/notification_spec.rb +198 -0
  63. data/spec/unit/app_spec.rb +18 -0
  64. data/spec/unit/configuration_spec.rb +38 -0
  65. data/spec/unit/daemon/apns/app_runner_spec.rb +39 -0
  66. data/spec/unit/daemon/apns/connection_spec.rb +234 -0
  67. data/spec/unit/daemon/apns/delivery_handler_spec.rb +48 -0
  68. data/spec/unit/daemon/apns/delivery_spec.rb +160 -0
  69. data/spec/unit/daemon/apns/disconnection_error_spec.rb +18 -0
  70. data/spec/unit/daemon/apns/feedback_receiver_spec.rb +118 -0
  71. data/spec/unit/daemon/app_runner_shared.rb +66 -0
  72. data/spec/unit/daemon/app_runner_spec.rb +129 -0
  73. data/spec/unit/daemon/database_reconnectable_spec.rb +109 -0
  74. data/spec/unit/daemon/delivery_error_spec.rb +13 -0
  75. data/spec/unit/daemon/delivery_handler_shared.rb +28 -0
  76. data/spec/unit/daemon/delivery_queue_spec.rb +29 -0
  77. data/spec/unit/daemon/feeder_spec.rb +95 -0
  78. data/spec/unit/daemon/gcm/app_runner_spec.rb +17 -0
  79. data/spec/unit/daemon/gcm/delivery_handler_spec.rb +36 -0
  80. data/spec/unit/daemon/gcm/delivery_spec.rb +236 -0
  81. data/spec/unit/daemon/interruptible_sleep_spec.rb +40 -0
  82. data/spec/unit/daemon/logger_spec.rb +156 -0
  83. data/spec/unit/daemon_spec.rb +139 -0
  84. data/spec/unit/gcm/app_spec.rb +5 -0
  85. data/spec/unit/gcm/notification_spec.rb +55 -0
  86. data/spec/unit/notification_shared.rb +38 -0
  87. data/spec/unit/notification_spec.rb +6 -0
  88. data/spec/unit_spec_helper.rb +145 -0
  89. metadata +240 -0
@@ -0,0 +1,46 @@
1
+ module Rapns
2
+ module Daemon
3
+ class DeliveryHandler
4
+ attr_accessor :queue
5
+
6
+ def start
7
+ @thread = Thread.new do
8
+ loop do
9
+ handle_next_notification
10
+ break if @stop
11
+ end
12
+ end
13
+ end
14
+
15
+ def stop
16
+ @stop = true
17
+ if @thread
18
+ queue.wakeup(@thread)
19
+ @thread.join
20
+ end
21
+ stopped
22
+ end
23
+
24
+ protected
25
+
26
+ def stopped
27
+ end
28
+
29
+ def handle_next_notification
30
+ begin
31
+ notification = queue.pop
32
+ rescue DeliveryQueue::WakeupError
33
+ return
34
+ end
35
+
36
+ begin
37
+ deliver(notification)
38
+ rescue StandardError => e
39
+ Rapns::Daemon.logger.error(e)
40
+ ensure
41
+ queue.notification_processed
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,42 @@
1
+ module Rapns
2
+ module Daemon
3
+ if RUBY_VERSION < '1.9'
4
+ require 'rapns/daemon/delivery_queue_18'
5
+ ancestor_class = DeliveryQueue18
6
+ else
7
+ require 'rapns/daemon/delivery_queue_19'
8
+ ancestor_class = DeliveryQueue19
9
+ end
10
+
11
+ class DeliveryQueue < ancestor_class
12
+ class WakeupError < StandardError; end
13
+
14
+ def initialize
15
+ @num_notifications = 0
16
+ @queue = []
17
+ @waiting = []
18
+
19
+ super
20
+ end
21
+
22
+ def wakeup(thread)
23
+ synchronize do
24
+ t = @waiting.delete(thread)
25
+ t.raise WakeupError if t
26
+ end
27
+ end
28
+
29
+ def size
30
+ synchronize { @queue.size }
31
+ end
32
+
33
+ def notification_processed
34
+ synchronize { @num_notifications -= 1 }
35
+ end
36
+
37
+ def notifications_processed?
38
+ synchronize { @num_notifications == 0 }
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,44 @@
1
+ module Rapns
2
+ module Daemon
3
+ class DeliveryQueue18
4
+ def push(obj)
5
+ Thread.critical = true
6
+ @queue.push obj
7
+ @num_notifications += 1
8
+ begin
9
+ t = @waiting.shift
10
+ t.wakeup if t
11
+ rescue ThreadError
12
+ retry
13
+ ensure
14
+ Thread.critical = false
15
+ end
16
+ begin
17
+ t.run if t
18
+ rescue ThreadError
19
+ end
20
+ end
21
+
22
+ def pop
23
+ while (Thread.critical = true; @queue.empty?)
24
+ @waiting.push Thread.current
25
+ Thread.stop
26
+ end
27
+ @queue.shift
28
+ ensure
29
+ Thread.critical = false
30
+ end
31
+
32
+ protected
33
+
34
+ def synchronize
35
+ Thread.critical = true
36
+ begin
37
+ yield
38
+ ensure
39
+ Thread.critical = false
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,42 @@
1
+ module Rapns
2
+ module Daemon
3
+ class DeliveryQueue19
4
+ def initialize
5
+ @mutex = Mutex.new
6
+ end
7
+
8
+ def push(notification)
9
+ @mutex.synchronize do
10
+ @num_notifications += 1
11
+ @queue.push(notification)
12
+
13
+ begin
14
+ t = @waiting.shift
15
+ t.wakeup if t
16
+ rescue ThreadError
17
+ retry
18
+ end
19
+ end
20
+ end
21
+
22
+ def pop
23
+ @mutex.synchronize do
24
+ while true
25
+ if @queue.empty?
26
+ @waiting.push Thread.current
27
+ @mutex.sleep
28
+ else
29
+ return @queue.shift
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ protected
36
+
37
+ def synchronize(&blk)
38
+ @mutex.synchronize(&blk)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,37 @@
1
+ module Rapns
2
+ module Daemon
3
+ class Feeder
4
+ extend InterruptibleSleep
5
+ extend DatabaseReconnectable
6
+
7
+ def self.start(poll)
8
+ loop do
9
+ enqueue_notifications
10
+ interruptible_sleep poll
11
+ break if @stop
12
+ end
13
+ end
14
+
15
+ def self.stop
16
+ @stop = true
17
+ interrupt_sleep
18
+ end
19
+
20
+ protected
21
+
22
+ def self.enqueue_notifications
23
+ begin
24
+ with_database_reconnect_and_retry do
25
+ batch_size = Rapns.config.batch_size
26
+ idle = Rapns::Daemon::AppRunner.idle.map(&:app)
27
+ Rapns::Notification.ready_for_delivery.for_apps(idle).limit(batch_size).each do |notification|
28
+ Rapns::Daemon::AppRunner.enqueue(notification)
29
+ end
30
+ end
31
+ rescue StandardError => e
32
+ Rapns::Daemon.logger.error(e)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,13 @@
1
+ module Rapns
2
+ module Daemon
3
+ module Gcm
4
+ class AppRunner < Rapns::Daemon::AppRunner
5
+ protected
6
+
7
+ def new_delivery_handler
8
+ DeliveryHandler.new(app)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,206 @@
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
+
11
+ def initialize(app, http, notification)
12
+ @app = app
13
+ @http = http
14
+ @notification = notification
15
+ end
16
+
17
+ def perform
18
+ begin
19
+ handle_response(do_post)
20
+ rescue Rapns::DeliveryError => error
21
+ mark_failed(error.code, error.description)
22
+ raise
23
+ end
24
+ end
25
+
26
+ protected
27
+
28
+ def handle_response(response)
29
+ case response.code.to_i
30
+ when 200
31
+ ok(response)
32
+ when 400
33
+ bad_request(response)
34
+ when 401
35
+ unauthorized(response)
36
+ when 500
37
+ internal_server_error(response)
38
+ when 503
39
+ service_unavailable(response)
40
+ else
41
+ raise Rapns::DeliveryError.new(response.code, @notification.id, HTTP_STATUS_CODES[response.code.to_i])
42
+ end
43
+ end
44
+
45
+ def ok(response)
46
+ body = multi_json_load(response.body)
47
+
48
+ if body['failure'].to_i == 0
49
+ mark_delivered
50
+ Rapns::Daemon.logger.info("[#{@app.name}] #{@notification.id} sent to #{@notification.registration_ids.join(', ')}")
51
+ else
52
+ handle_errors(response, body)
53
+ end
54
+ end
55
+
56
+ def handle_errors(response, body)
57
+ errors = {}
58
+
59
+ body['results'].each_with_index do |result, i|
60
+ errors[i] = result['error'] if result['error']
61
+ end
62
+
63
+ if body['success'].to_i == 0 && errors.values.all? { |error| error.in?(UNAVAILABLE_STATES) }
64
+ all_devices_unavailable(response)
65
+ elsif errors.values.any? { |error| error.in?(UNAVAILABLE_STATES) }
66
+ some_devices_unavailable(response, errors)
67
+ else
68
+ raise Rapns::DeliveryError.new(nil, @notification.id, describe_errors(errors))
69
+ end
70
+ end
71
+
72
+ def bad_request(response)
73
+ raise Rapns::DeliveryError.new(400, @notification.id, 'GCM failed to parse the JSON request. Possibly an rapns bug, please open an issue.')
74
+ end
75
+
76
+ def unauthorized(response)
77
+ raise Rapns::DeliveryError.new(401, @notification.id, 'Unauthorized, check your App auth_key.')
78
+ end
79
+
80
+ def internal_server_error(response)
81
+ retry_delivery(@notification, response)
82
+ Rapns::Daemon.logger.warn("GCM responded with an Internal Error. " + retry_message)
83
+ end
84
+
85
+ def service_unavailable(response)
86
+ retry_delivery(@notification, response)
87
+ Rapns::Daemon.logger.warn("GCM responded with an Service Unavailable Error. " + retry_message)
88
+ end
89
+
90
+ def all_devices_unavailable(response)
91
+ retry_delivery(@notification, response)
92
+ Rapns::Daemon.logger.warn("All recipients unavailable. " + retry_message)
93
+ end
94
+
95
+ def some_devices_unavailable(response, errors)
96
+ unavailable_idxs = errors.find_all { |i, error| error.in?(UNAVAILABLE_STATES) }.map(&:first)
97
+ new_notification = build_new_notification(response, unavailable_idxs)
98
+ with_database_reconnect_and_retry { new_notification.save! }
99
+ raise Rapns::DeliveryError.new(nil, @notification.id,
100
+ describe_errors(errors) + " #{unavailable_idxs.join(', ')} will be retried as notification #{new_notification.id}.")
101
+ end
102
+
103
+ def build_new_notification(response, idxs)
104
+ notification = Rapns::Gcm::Notification.new
105
+ notification.assign_attributes(@notification.attributes.slice('app_id', 'collapse_key', 'delay_while_idle'))
106
+ notification.data = @notification.data
107
+ notification.registration_ids = idxs.map { |i| @notification.registration_ids[i] }
108
+ notification.deliver_after = deliver_after_header(response)
109
+ notification
110
+ end
111
+
112
+ def deliver_after_header(response)
113
+ if response.header['retry-after']
114
+ retry_after = if response.header['retry-after'].to_s =~ /^[0-9]+$/
115
+ Time.now + response.header['retry-after'].to_i
116
+ else
117
+ Time.httpdate(response.header['retry-after'])
118
+ end
119
+ end
120
+ end
121
+
122
+ def retry_delivery(notification, response)
123
+ if time = deliver_after_header(response)
124
+ retry_after(notification, time)
125
+ else
126
+ retry_exponentially(notification)
127
+ end
128
+ end
129
+
130
+ def describe_errors(errors)
131
+ description = if errors.size == @notification.registration_ids.size
132
+ "Failed to deliver to all recipients. Errors: #{errors.values.join(', ')}."
133
+ else
134
+ "Failed to deliver to recipients #{errors.keys.join(', ')}. Errors: #{errors.values.join(', ')}."
135
+ end
136
+ end
137
+
138
+ def retry_message
139
+ "Notification #{@notification.id} will be retired after #{@notification.deliver_after.strftime("%Y-%m-%d %H:%M:%S")} (retry #{@notification.retries})."
140
+ end
141
+
142
+ def do_post
143
+ post = Net::HTTP::Post.new(GCM_URI.path, initheader = {'Content-Type' => 'application/json',
144
+ 'Authorization' => "key=#{@notification.app.auth_key}"})
145
+ post.body = @notification.as_json.to_json
146
+ @http.request(GCM_URI, post)
147
+ end
148
+
149
+ HTTP_STATUS_CODES = {
150
+ 100 => 'Continue',
151
+ 101 => 'Switching Protocols',
152
+ 102 => 'Processing',
153
+ 200 => 'OK',
154
+ 201 => 'Created',
155
+ 202 => 'Accepted',
156
+ 203 => 'Non-Authoritative Information',
157
+ 204 => 'No Content',
158
+ 205 => 'Reset Content',
159
+ 206 => 'Partial Content',
160
+ 207 => 'Multi-Status',
161
+ 226 => 'IM Used',
162
+ 300 => 'Multiple Choices',
163
+ 301 => 'Moved Permanently',
164
+ 302 => 'Found',
165
+ 303 => 'See Other',
166
+ 304 => 'Not Modified',
167
+ 305 => 'Use Proxy',
168
+ 306 => 'Reserved',
169
+ 307 => 'Temporary Redirect',
170
+ 400 => 'Bad Request',
171
+ 401 => 'Unauthorized',
172
+ 402 => 'Payment Required',
173
+ 403 => 'Forbidden',
174
+ 404 => 'Not Found',
175
+ 405 => 'Method Not Allowed',
176
+ 406 => 'Not Acceptable',
177
+ 407 => 'Proxy Authentication Required',
178
+ 408 => 'Request Timeout',
179
+ 409 => 'Conflict',
180
+ 410 => 'Gone',
181
+ 411 => 'Length Required',
182
+ 412 => 'Precondition Failed',
183
+ 413 => 'Request Entity Too Large',
184
+ 414 => 'Request-URI Too Long',
185
+ 415 => 'Unsupported Media Type',
186
+ 416 => 'Requested Range Not Satisfiable',
187
+ 417 => 'Expectation Failed',
188
+ 418 => "I'm a Teapot",
189
+ 422 => 'Unprocessable Entity',
190
+ 423 => 'Locked',
191
+ 424 => 'Failed Dependency',
192
+ 426 => 'Upgrade Required',
193
+ 500 => 'Internal Server Error',
194
+ 501 => 'Not Implemented',
195
+ 502 => 'Bad Gateway',
196
+ 503 => 'Service Unavailable',
197
+ 504 => 'Gateway Timeout',
198
+ 505 => 'HTTP Version Not Supported',
199
+ 506 => 'Variant Also Negotiates',
200
+ 507 => 'Insufficient Storage',
201
+ 510 => 'Not Extended',
202
+ }
203
+ end
204
+ end
205
+ end
206
+ 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)
11
+ Rapns::Daemon::Gcm::Delivery.perform(@app, @http, notification)
12
+ end
13
+
14
+ def stopped
15
+ @http.shutdown
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,18 @@
1
+ module Rapns
2
+ module Daemon
3
+ module InterruptibleSleep
4
+ def interruptible_sleep(seconds)
5
+ @_sleep_check, @_sleep_interrupt = IO.pipe
6
+ IO.select([@_sleep_check], nil, nil, seconds)
7
+ @_sleep_check.close rescue IOError
8
+ @_sleep_interrupt.close rescue IOError
9
+ end
10
+
11
+ def interrupt_sleep
12
+ if @_sleep_interrupt
13
+ @_sleep_interrupt.close rescue IOError
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,68 @@
1
+ module Rapns
2
+ module Daemon
3
+ class Logger
4
+ def initialize(options)
5
+ @options = options
6
+
7
+ begin
8
+ log = File.open(File.join(Rails.root, 'log', 'rapns.log'), 'a')
9
+ log.sync = true
10
+ @logger = ActiveSupport::BufferedLogger.new(log, Rails.logger.level)
11
+ @logger.auto_flushing = Rails.logger.respond_to?(:auto_flushing) ? Rails.logger.auto_flushing : true
12
+ rescue Errno::ENOENT, Errno::EPERM => e
13
+ @logger = nil
14
+ error(e)
15
+ error('Logging disabled.')
16
+ end
17
+ end
18
+
19
+ def info(msg)
20
+ log(:info, msg)
21
+ end
22
+
23
+ def error(msg, options = {})
24
+ airbrake_notify(msg) if notify_via_airbrake?(msg, options)
25
+ log(:error, msg, 'ERROR', STDERR)
26
+ end
27
+
28
+ def warn(msg)
29
+ log(:warn, msg, 'WARNING', STDERR)
30
+ end
31
+
32
+ private
33
+
34
+ def log(where, msg, prefix = nil, io = STDOUT)
35
+ if msg.is_a?(Exception)
36
+ formatted_backtrace = msg.backtrace.join("\n")
37
+ msg = "#{msg.class.name}, #{msg.message}\n#{formatted_backtrace}"
38
+ end
39
+
40
+ formatted_msg = "[#{Time.now.to_s(:db)}] "
41
+ formatted_msg << "[#{prefix}] " if prefix
42
+ formatted_msg << msg
43
+
44
+ if io == STDERR
45
+ io.puts formatted_msg
46
+ elsif @options[:foreground]
47
+ io.puts formatted_msg
48
+ end
49
+
50
+ @logger.send(where, formatted_msg) if @logger
51
+ end
52
+
53
+ def airbrake_notify(e)
54
+ return unless @options[:airbrake_notify] == true
55
+
56
+ if defined?(Airbrake)
57
+ Airbrake.notify_or_ignore(e)
58
+ elsif defined?(HoptoadNotifier)
59
+ HoptoadNotifier.notify_or_ignore(e)
60
+ end
61
+ end
62
+
63
+ def notify_via_airbrake?(msg, options)
64
+ msg.is_a?(Exception) && options[:airbrake_notify] != false
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,136 @@
1
+ require 'thread'
2
+ require 'socket'
3
+ require 'pathname'
4
+ require 'openssl'
5
+
6
+ require 'net/http/persistent'
7
+
8
+ require 'rapns/daemon/interruptible_sleep'
9
+ require 'rapns/daemon/delivery_error'
10
+ require 'rapns/daemon/database_reconnectable'
11
+ require 'rapns/daemon/delivery'
12
+ require 'rapns/daemon/delivery_queue'
13
+ require 'rapns/daemon/feeder'
14
+ require 'rapns/daemon/logger'
15
+ require 'rapns/daemon/app_runner'
16
+ require 'rapns/daemon/delivery_handler'
17
+
18
+ require 'rapns/daemon/apns/delivery'
19
+ require 'rapns/daemon/apns/disconnection_error'
20
+ require 'rapns/daemon/apns/connection'
21
+ require 'rapns/daemon/apns/app_runner'
22
+ require 'rapns/daemon/apns/delivery_handler'
23
+ require 'rapns/daemon/apns/feedback_receiver'
24
+
25
+ require 'rapns/daemon/gcm/delivery'
26
+ require 'rapns/daemon/gcm/app_runner'
27
+ require 'rapns/daemon/gcm/delivery_handler'
28
+
29
+ module Rapns
30
+ module Daemon
31
+ extend DatabaseReconnectable
32
+
33
+ class << self
34
+ attr_accessor :logger
35
+ end
36
+
37
+ def self.start
38
+ self.logger = Logger.new(:foreground => Rapns.config.foreground,
39
+ :airbrake_notify => Rapns.config.airbrake_notify)
40
+ setup_signal_hooks
41
+
42
+ unless Rapns.config.foreground
43
+ daemonize
44
+ reconnect_database
45
+ end
46
+
47
+ write_pid_file
48
+ ensure_upgraded
49
+ AppRunner.sync
50
+ Feeder.start(Rapns.config.push_poll)
51
+ end
52
+
53
+ protected
54
+
55
+ def self.ensure_upgraded
56
+ count = 0
57
+
58
+ begin
59
+ count = Rapns::App.count
60
+ rescue ActiveRecord::StatementInvalid
61
+ puts "!!!! RAPNS NOT STARTED !!!!"
62
+ puts
63
+ puts "As of version v2.0.0 apps are configured in the database instead of rapns.yml."
64
+ puts "Please run 'rails g rapns' to generate the new migrations and create your app."
65
+ puts "See https://github.com/ileitch/rapns for further instructions."
66
+ puts
67
+ exit 1
68
+ end
69
+
70
+ if count == 0
71
+ logger.warn("You have not created an app yet. See https://github.com/ileitch/rapns for instructions.")
72
+ end
73
+
74
+ if File.exists?(File.join(Rails.root, 'config', 'rapns', 'rapns.yml'))
75
+ logger.warn(<<-EOS)
76
+ Since 2.0.0 rapns uses command-line options and a Ruby based configuration file.
77
+ Please run 'rails g rapns' to generate a new configuration file into config/initializers.
78
+ Remove config/rapns/rapns.yml to avoid this warning.
79
+ EOS
80
+ end
81
+ end
82
+
83
+ def self.setup_signal_hooks
84
+ @shutting_down = false
85
+
86
+ Signal.trap('SIGHUP') { AppRunner.sync }
87
+ Signal.trap('SIGUSR2') { AppRunner.debug }
88
+
89
+ ['SIGINT', 'SIGTERM'].each do |signal|
90
+ Signal.trap(signal) { handle_shutdown_signal }
91
+ end
92
+ end
93
+
94
+ def self.handle_shutdown_signal
95
+ exit 1 if @shutting_down
96
+ @shutting_down = true
97
+ shutdown
98
+ end
99
+
100
+ def self.shutdown
101
+ puts "\nShutting down..."
102
+ Feeder.stop
103
+ AppRunner.stop
104
+ delete_pid_file
105
+ end
106
+
107
+ def self.write_pid_file
108
+ if !Rapns.config.pid_file.blank?
109
+ begin
110
+ File.open(Rapns.config.pid_file, 'w') { |f| f.puts Process.pid }
111
+ rescue SystemCallError => e
112
+ logger.error("Failed to write PID to '#{Rapns.config.pid_file}': #{e.inspect}")
113
+ end
114
+ end
115
+ end
116
+
117
+ def self.delete_pid_file
118
+ pid_file = Rapns.config.pid_file
119
+ File.delete(pid_file) if !pid_file.blank? && File.exists?(pid_file)
120
+ end
121
+
122
+ # :nocov:
123
+ def self.daemonize
124
+ exit if pid = fork
125
+ Process.setsid
126
+ exit if pid = fork
127
+
128
+ Dir.chdir '/'
129
+ File.umask 0000
130
+
131
+ STDIN.reopen '/dev/null'
132
+ STDOUT.reopen '/dev/null', 'a'
133
+ STDERR.reopen STDOUT
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,7 @@
1
+ module Rapns
2
+ module Gcm
3
+ class App < Rapns::App
4
+ validates :auth_key, :presence => true
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,11 @@
1
+ module Rapns
2
+ module Gcm
3
+ class ExpiryCollapseKeyMutualInclusionValidator < ActiveModel::Validator
4
+ def validate(record)
5
+ if record.collapse_key && !record.expiry
6
+ record.errors[:expiry] << "must be set when using a collapse_key"
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end