rapns 3.0.0-java

Sign up to get free protection for your applications and to get access to all the features.
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