rapns 3.3.2-java → 3.4.0-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. data/CHANGELOG.md +7 -0
  2. data/README.md +19 -21
  3. data/bin/rapns +14 -13
  4. data/lib/generators/templates/rapns.rb +8 -4
  5. data/lib/rapns.rb +7 -0
  6. data/lib/rapns/TODO +3 -0
  7. data/lib/rapns/apns/feedback.rb +4 -2
  8. data/lib/rapns/app.rb +3 -1
  9. data/lib/rapns/configuration.rb +8 -1
  10. data/lib/rapns/daemon.rb +3 -1
  11. data/lib/rapns/daemon/apns/app_runner.rb +3 -2
  12. data/lib/rapns/daemon/apns/certificate_expired_error.rb +20 -0
  13. data/lib/rapns/daemon/apns/connection.rb +26 -0
  14. data/lib/rapns/daemon/apns/delivery.rb +2 -1
  15. data/lib/rapns/daemon/apns/delivery_handler.rb +2 -2
  16. data/lib/rapns/daemon/app_runner.rb +50 -28
  17. data/lib/rapns/daemon/batch.rb +100 -0
  18. data/lib/rapns/daemon/delivery.rb +6 -10
  19. data/lib/rapns/daemon/delivery_handler.rb +14 -12
  20. data/lib/rapns/daemon/delivery_handler_collection.rb +33 -0
  21. data/lib/rapns/daemon/feeder.rb +3 -5
  22. data/lib/rapns/daemon/gcm/delivery.rb +5 -4
  23. data/lib/rapns/daemon/gcm/delivery_handler.rb +2 -2
  24. data/lib/rapns/daemon/store/active_record.rb +23 -2
  25. data/lib/rapns/deprecation.rb +7 -6
  26. data/lib/rapns/logger.rb +1 -1
  27. data/lib/rapns/notification.rb +5 -3
  28. data/lib/rapns/reflection.rb +1 -1
  29. data/lib/rapns/version.rb +1 -1
  30. data/lib/tasks/cane.rake +1 -1
  31. data/lib/tasks/test.rake +8 -3
  32. data/spec/unit/apns/app_spec.rb +4 -4
  33. data/spec/unit/apns_feedback_spec.rb +1 -1
  34. data/spec/unit/configuration_spec.rb +12 -6
  35. data/spec/unit/daemon/apns/app_runner_spec.rb +6 -4
  36. data/spec/unit/daemon/apns/certificate_expired_error_spec.rb +11 -0
  37. data/spec/unit/daemon/apns/connection_spec.rb +46 -10
  38. data/spec/unit/daemon/apns/delivery_handler_spec.rb +24 -18
  39. data/spec/unit/daemon/apns/delivery_spec.rb +11 -12
  40. data/spec/unit/daemon/apns/feedback_receiver_spec.rb +16 -16
  41. data/spec/unit/daemon/app_runner_shared.rb +27 -10
  42. data/spec/unit/daemon/app_runner_spec.rb +48 -28
  43. data/spec/unit/daemon/batch_spec.rb +160 -0
  44. data/spec/unit/daemon/delivery_handler_collection_spec.rb +37 -0
  45. data/spec/unit/daemon/delivery_handler_shared.rb +20 -11
  46. data/spec/unit/daemon/feeder_spec.rb +12 -12
  47. data/spec/unit/daemon/gcm/app_runner_spec.rb +4 -2
  48. data/spec/unit/daemon/gcm/delivery_handler_spec.rb +18 -10
  49. data/spec/unit/daemon/gcm/delivery_spec.rb +47 -17
  50. data/spec/unit/daemon/interruptible_sleep_spec.rb +3 -3
  51. data/spec/unit/daemon/reflectable_spec.rb +1 -1
  52. data/spec/unit/daemon/store/active_record/reconnectable_spec.rb +1 -1
  53. data/spec/unit/daemon/store/active_record_spec.rb +87 -10
  54. data/spec/unit/daemon_spec.rb +6 -6
  55. data/spec/unit/deprecation_spec.rb +2 -2
  56. data/spec/unit/logger_spec.rb +33 -17
  57. data/spec/unit/notification_shared.rb +7 -3
  58. data/spec/unit/upgraded_spec.rb +8 -14
  59. data/spec/unit_spec_helper.rb +9 -1
  60. metadata +53 -44
  61. data/lib/rapns/daemon/delivery_queue.rb +0 -42
  62. data/lib/rapns/daemon/delivery_queue_18.rb +0 -44
  63. data/lib/rapns/daemon/delivery_queue_19.rb +0 -42
  64. data/spec/acceptance/gcm_upgrade_spec.rb +0 -34
  65. data/spec/acceptance_spec_helper.rb +0 -85
  66. data/spec/unit/daemon/delivery_queue_spec.rb +0 -29
@@ -1,3 +1,10 @@
1
+ ### 3.4.0 (Aug 28, 2013)
2
+ * Rails 4 support.
3
+ * Add apns_certificate_will_expire reflection.
4
+ * Perform storage update in batches where possible, to increase throughput.
5
+ * airbrake_notify is now deprecated, use the Reflection API instead.
6
+ * Fix calling the notification_delivered reflection twice (#149).
7
+
1
8
  ## 3.3.2 (June 30, 2013)
2
9
  * Fix Rails 3.0.x compatibility (#138) (@yoppi).
3
10
  * Ensure Rails does not set a default value for text columns (#137).
data/README.md CHANGED
@@ -4,19 +4,16 @@
4
4
  ### Rapns - Professional grade APNs and GCM for Ruby.
5
5
 
6
6
  * Supports both APNs (iOS) and GCM (Google Cloud Messaging, Android).
7
- * Seamless Rails integration.
8
- * Scalable - choose the number of threads each app spawns.
7
+ * Seamless Rails (3, 4) integration.
8
+ * Scalable - choose the number of persistent connections for each app.
9
9
  * Designed for uptime - signal -HUP to add, update apps.
10
10
  * Stable - reconnects database and network connections when lost.
11
- * Run as a daemon or inside an existing process.
11
+ * Run as a daemon or inside an [existing processs](https://github.com/ileitch/rapns/wiki/Embedding-API).
12
12
  * Use in a scheduler for low-workload deployments ([Push API](https://github.com/ileitch/rapns/wiki/Push-API)).
13
- * Reflection API for fine-grained instrumentation ([Reflection API](https://github.com/ileitch/rapns/wiki/Relfection-API)).
13
+ * Reflection API for fine-grained instrumentation and error handling ([Reflection API](https://github.com/ileitch/rapns/wiki/Reflection-API)).
14
14
  * Works with MRI, JRuby, Rubinius 1.8 and 1.9.
15
- * [Airbrake](http://airbrakeapp.com/) integration.
16
15
  * Built with love.
17
16
 
18
- #### 2.x users please read [upgrading from 2.x to 3.0](https://github.com/ileitch/rapns/wiki/Upgrading-from-version-2.x-to-3.0)
19
-
20
17
  ### Who uses Rapns?
21
18
 
22
19
  [GateGuru](http://gateguruapp.com) and [Desk.com](http://desk.com), among others!
@@ -34,7 +31,7 @@ Generate the migrations, rapns.yml and migrate:
34
31
  rails g rapns
35
32
  rake db:migrate
36
33
 
37
- ## Create an App
34
+ ## Create an App & Notification
38
35
 
39
36
  #### APNs
40
37
 
@@ -44,24 +41,12 @@ If this is your first time using the APNs, you will need to generate SSL certifi
44
41
  app = Rapns::Apns::App.new
45
42
  app.name = "ios_app"
46
43
  app.certificate = File.read("/path/to/sandbox.pem")
47
- app.environment = "sandbox"
44
+ app.environment = "sandbox" # APNs environment.
48
45
  app.password = "certificate password"
49
46
  app.connections = 1
50
47
  app.save!
51
48
  ```
52
49
 
53
- #### GCM
54
- ```ruby
55
- app = Rapns::Gcm::App.new
56
- app.name = "android_app"
57
- app.auth_key = "..."
58
- app.connections = 1
59
- app.save!
60
- ```
61
-
62
- ## Create a Notification
63
-
64
- #### APNs
65
50
  ```ruby
66
51
  n = Rapns::Apns::Notification.new
67
52
  n.app = Rapns::Apns::App.find_by_name("ios_app")
@@ -71,7 +56,18 @@ n.attributes_for_device = {:foo => :bar}
71
56
  n.save!
72
57
  ```
73
58
 
59
+ You should also implement the [apns_certificate_will_expire](https://github.com/ileitch/rapns/wiki/Reflection-API) reflection to monitor when your certificate is due to expire.
60
+
74
61
  #### GCM
62
+
63
+ ```ruby
64
+ app = Rapns::Gcm::App.new
65
+ app.name = "android_app"
66
+ app.auth_key = "..."
67
+ app.connections = 1
68
+ app.save!
69
+ ```
70
+
75
71
  ```ruby
76
72
  n = Rapns::Gcm::Notification.new
77
73
  n.app = Rapns::Gcm::App.find_by_name("android_app")
@@ -80,6 +76,8 @@ n.data = {:message => "hi mom!"}
80
76
  n.save!
81
77
  ```
82
78
 
79
+ GCM also requires you to respond to [Canonical IDs](https://github.com/ileitch/rapns/wiki/Canonical-IDs).
80
+
83
81
  ## Starting Rapns
84
82
 
85
83
  As a daemon:
data/bin/rapns CHANGED
@@ -5,28 +5,29 @@ require 'rapns'
5
5
 
6
6
  environment = ARGV[0]
7
7
 
8
- banner = 'Usage: rapns <Rails environment> [options]'
9
- if environment.nil? || environment =~ /^-/
10
- puts banner
11
- exit 1
12
- end
13
-
14
8
  config = Rapns::ConfigurationWithoutDefaults.new
15
9
 
16
- ARGV.options do |opts|
17
- opts.banner = banner
10
+ options = ARGV.options do |opts|
11
+ opts.banner = 'Usage: rapns <Rails environment> [options]'
18
12
  opts.on('-f', '--foreground', 'Run in the foreground.') { config.foreground = true }
19
- opts.on('-P N', '--db-poll N', Integer, "Frequency in seconds to check for new notifications. Default: #{config.push_poll}.") { |n| config.push_poll = n }
20
- opts.on('-F N', '--feedback-poll N', Integer, "Frequency in seconds to check for feedback. Default: #{config.feedback_poll}.") { |n| config.feedback_poll = n }
13
+ opts.on('-P N', '--db-poll N', Integer, "Frequency in seconds to check for new notifications.") { |n| config.push_poll = n }
14
+ opts.on('-F N', '--feedback-poll N', Integer, "Frequency in seconds to check for feedback.") { |n| config.feedback_poll = n }
21
15
  opts.on('-e', '--no-error-checks', 'Disable APNs error checking after notification delivery.') { config.check_for_errors = false }
22
16
  opts.on('-n', '--no-airbrake-notify', 'Disables error notifications via Airbrake.') { config.airbrake_notify = false }
23
17
  opts.on('-p PATH', '--pid-file PATH', String, 'Path to write PID file. Relative to Rails root unless absolute.') { |path| config.pid_file = path }
24
- opts.on('-b N', '--batch-size N', Integer, 'ActiveRecord notifications batch size.') { |n| config.batch_size = n }
25
- opts.on('-v', '--version', 'Print this version of rapns.') { puts "rapns #{Rapns::VERSION}"; exit }
18
+ opts.on('-b N', '--batch-size N', Integer, 'Storage backend notification batch size.') { |n| config.batch_size = n }
19
+ opts.on('-B', '--[no-]batch-storage-updates', 'Perform storage updates in batches.') { |v| config.batch_storage_updates = v }
20
+ opts.on('-v', '--version', 'Print the version.') { puts "rapns #{Rapns::VERSION}"; exit }
26
21
  opts.on('-h', '--help', 'You\'re looking at it.') { puts opts; exit }
27
- opts.parse!
28
22
  end
29
23
 
24
+ if environment.nil? || environment =~ /^-/
25
+ puts options.to_s
26
+ exit 1
27
+ end
28
+
29
+ options.parse!
30
+
30
31
  ENV['RAILS_ENV'] = environment
31
32
  load 'config/environment.rb'
32
33
  load 'config/initializers/rapns.rb' if File.exist?('config/initializers/rapns.rb')
@@ -11,15 +11,15 @@
11
11
  # Frequency in seconds to check for feedback
12
12
  # config.feedback_poll = 60
13
13
 
14
- # Enable/Disable error notifications via Airbrake.
15
- # config.airbrake_notify = true
16
-
17
14
  # Disable APNs error checking after notification delivery.
18
15
  # config.check_for_errors = true
19
16
 
20
17
  # ActiveRecord notifications batch size.
21
18
  # config.batch_size = 5000
22
19
 
20
+ # Perform updates to the storage backend in batches to reduce IO.
21
+ # config.batch_storage_updates = true
22
+
23
23
  # Path to write PID file. Relative to Rails root unless absolute.
24
24
  # config.pid_file = '/path/to/rapns.pid'
25
25
 
@@ -71,8 +71,12 @@ Rapns.reflect do |on|
71
71
  # on.gcm_canonical_id do |old_id, canonical_id|
72
72
  # end
73
73
 
74
+ # Called when an APNs certificate will expire within 1 month.
75
+ # Implement on.error to catch errors raised when the certificate expires.
76
+ # on.apns_certificate_will_expire do |app, expiration_time|
77
+ # end
78
+
74
79
  # Called when an exception is raised.
75
80
  # on.error do |error|
76
81
  # end
77
-
78
82
  end
@@ -1,6 +1,13 @@
1
1
  require 'active_record'
2
2
  require 'multi_json'
3
3
 
4
+ module Rapns
5
+ def self.attr_accessible_available?
6
+ require 'rails'
7
+ ::Rails::VERSION::STRING < '4'
8
+ end
9
+ end
10
+
4
11
  require 'rapns/version'
5
12
  require 'rapns/deprecation'
6
13
  require 'rapns/deprecatable'
@@ -0,0 +1,3 @@
1
+ * Should internal errors mark a notification as failed?
2
+ * Check cert expiry when connected.
3
+ * Lazy connect on socket write? - would allow to catch initial expiry error and mark on notification.
@@ -3,7 +3,9 @@ module Rapns
3
3
  class Feedback < ActiveRecord::Base
4
4
  self.table_name = 'rapns_feedback'
5
5
 
6
- attr_accessible :device_token, :failed_at, :app
6
+ if Rapns.attr_accessible_available?
7
+ attr_accessible :device_token, :failed_at, :app
8
+ end
7
9
 
8
10
  validates :device_token, :presence => true
9
11
  validates :failed_at, :presence => true
@@ -11,4 +13,4 @@ module Rapns
11
13
  validates_with Rapns::Apns::DeviceTokenFormatValidator
12
14
  end
13
15
  end
14
- end
16
+ end
@@ -2,7 +2,9 @@ module Rapns
2
2
  class App < ActiveRecord::Base
3
3
  self.table_name = 'rapns_apps'
4
4
 
5
- attr_accessible :name, :environment, :certificate, :password, :connections, :auth_key
5
+ if Rapns.attr_accessible_available?
6
+ attr_accessible :name, :environment, :certificate, :password, :connections, :auth_key
7
+ end
6
8
 
7
9
  has_many :notifications, :class_name => 'Rapns::Notification'
8
10
 
@@ -9,7 +9,7 @@ module Rapns
9
9
 
10
10
  CONFIG_ATTRS = [:foreground, :push_poll, :feedback_poll, :embedded,
11
11
  :airbrake_notify, :check_for_errors, :pid_file, :batch_size,
12
- :push, :store, :logger]
12
+ :push, :store, :logger, :batch_storage_updates]
13
13
 
14
14
  class ConfigurationWithoutDefaults < Struct.new(*CONFIG_ATTRS)
15
15
  end
@@ -31,6 +31,11 @@ module Rapns
31
31
  end
32
32
  end
33
33
 
34
+ def airbrake_notify=(bool)
35
+ Rapns::Deprecation.warn("airbrake_notify is deprecated. Please use the Rapns.reflect API instead.")
36
+ super(bool)
37
+ end
38
+
34
39
  def pid_file=(path)
35
40
  if path && !Pathname.new(path).absolute?
36
41
  super(File.join(Rails.root, path))
@@ -67,6 +72,7 @@ module Rapns
67
72
 
68
73
  self.push_poll = 2
69
74
  self.feedback_poll = 60
75
+ Rapns::Deprecation.muted { self.airbrake_notify = true }
70
76
  self.airbrake_notify = true
71
77
  self.check_for_errors = true
72
78
  self.batch_size = 5000
@@ -74,6 +80,7 @@ module Rapns
74
80
  self.apns_feedback_callback = nil
75
81
  self.store = :active_record
76
82
  self.logger = nil
83
+ self.batch_storage_updates = true
77
84
 
78
85
  # Internal options.
79
86
  self.embedded = false
@@ -9,13 +9,15 @@ require 'rapns/daemon/reflectable'
9
9
  require 'rapns/daemon/interruptible_sleep'
10
10
  require 'rapns/daemon/delivery_error'
11
11
  require 'rapns/daemon/delivery'
12
- require 'rapns/daemon/delivery_queue'
13
12
  require 'rapns/daemon/feeder'
13
+ require 'rapns/daemon/batch'
14
14
  require 'rapns/daemon/app_runner'
15
15
  require 'rapns/daemon/delivery_handler'
16
+ require 'rapns/daemon/delivery_handler_collection'
16
17
 
17
18
  require 'rapns/daemon/apns/delivery'
18
19
  require 'rapns/daemon/apns/disconnection_error'
20
+ require 'rapns/daemon/apns/certificate_expired_error'
19
21
  require 'rapns/daemon/apns/connection'
20
22
  require 'rapns/daemon/apns/app_runner'
21
23
  require 'rapns/daemon/apns/delivery_handler'
@@ -2,9 +2,10 @@ module Rapns
2
2
  module Daemon
3
3
  module Apns
4
4
  class AppRunner < Rapns::Daemon::AppRunner
5
+
5
6
  protected
6
7
 
7
- def started
8
+ def after_start
8
9
  unless Rapns.config.push
9
10
  poll = Rapns.config.feedback_poll
10
11
  @feedback_receiver = FeedbackReceiver.new(app, poll)
@@ -12,7 +13,7 @@ module Rapns
12
13
  end
13
14
  end
14
15
 
15
- def stopped
16
+ def after_stop
16
17
  @feedback_receiver.stop if @feedback_receiver
17
18
  end
18
19
 
@@ -0,0 +1,20 @@
1
+ module Rapns
2
+ module Apns
3
+ class CertificateExpiredError < StandardError
4
+ attr_reader :app, :time
5
+
6
+ def initialize(app, time)
7
+ @app = app
8
+ @time = time
9
+ end
10
+
11
+ def to_s
12
+ message
13
+ end
14
+
15
+ def message
16
+ "#{app.name} certificate expired at #{time}."
17
+ end
18
+ end
19
+ end
20
+ end
@@ -101,6 +101,8 @@ module Rapns
101
101
  end
102
102
 
103
103
  def connect_socket
104
+ check_certificate_expiration
105
+
104
106
  tcp_socket = TCPSocket.new(@host, @port)
105
107
  tcp_socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, 1)
106
108
  tcp_socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
@@ -110,6 +112,30 @@ module Rapns
110
112
  Rapns.logger.info("[#{@app.name}] Connected to #{@host}:#{@port}")
111
113
  [tcp_socket, ssl_socket]
112
114
  end
115
+
116
+ def check_certificate_expiration
117
+ cert = @ssl_context.cert
118
+ if certificate_expired?
119
+ Rapns.logger.error(certificate_msg('expired'))
120
+ raise Rapns::Apns::CertificateExpiredError.new(@app, cert.not_after)
121
+ elsif certificate_expires_soon?
122
+ Rapns.logger.warn(certificate_msg('will expire'))
123
+ reflect(:apns_certificate_will_expire, @app, cert.not_after)
124
+ end
125
+ end
126
+
127
+ def certificate_msg(msg)
128
+ time = @ssl_context.cert.not_after.utc.strftime("%Y-%m-%d %H:%M:%S %Z")
129
+ "[#{@app.name}] Certificate #{msg} at #{time}."
130
+ end
131
+
132
+ def certificate_expired?
133
+ @ssl_context.cert.not_after && @ssl_context.cert.not_after.utc < Time.now.utc
134
+ end
135
+
136
+ def certificate_expires_soon?
137
+ @ssl_context.cert.not_after && @ssl_context.cert.not_after.utc < (Time.now + 1.month).utc
138
+ end
113
139
  end
114
140
  end
115
141
  end
@@ -16,10 +16,11 @@ module Rapns
16
16
  255 => "None (unknown error)"
17
17
  }
18
18
 
19
- def initialize(app, conneciton, notification)
19
+ def initialize(app, conneciton, notification, batch)
20
20
  @app = app
21
21
  @connection = conneciton
22
22
  @notification = notification
23
+ @batch = batch
23
24
  end
24
25
 
25
26
  def perform
@@ -13,8 +13,8 @@ module Rapns
13
13
  @host, @port = HOSTS[@app.environment.to_sym]
14
14
  end
15
15
 
16
- def deliver(notification)
17
- Rapns::Daemon::Apns::Delivery.perform(@app, connection, notification)
16
+ def deliver(notification, batch)
17
+ Rapns::Daemon::Apns::Delivery.new(@app, connection, notification, batch).perform
18
18
  end
19
19
 
20
20
  def stopped
@@ -1,17 +1,23 @@
1
1
  module Rapns
2
2
  module Daemon
3
3
  class AppRunner
4
+ extend Reflectable
5
+ include Reflectable
6
+
4
7
  class << self
5
8
  attr_reader :runners
6
9
  end
7
10
 
8
11
  @runners = {}
9
12
 
10
- def self.enqueue(notification)
11
- if app = runners[notification.app_id]
12
- app.enqueue(notification)
13
- else
14
- Rapns.logger.error("No such app '#{notification.app_id}' for notification #{notification.id}.")
13
+ def self.enqueue(notifications)
14
+ notifications.group_by(&:app_id).each do |app_id, group|
15
+ batch = Batch.new(group)
16
+ if app = runners[app_id]
17
+ app.enqueue(batch)
18
+ else
19
+ Rapns.logger.error("No such app '#{app_id}' for notifications #{batch.describe}.")
20
+ end
15
21
  end
16
22
  end
17
23
 
@@ -33,6 +39,7 @@ module Rapns
33
39
  rescue StandardError => e
34
40
  Rapns.logger.error("[#{app.name}] Exception raised during startup. Notifications will not be delivered for this app.")
35
41
  Rapns.logger.error(e)
42
+ reflect(:error, e)
36
43
  end
37
44
  end
38
45
  end
@@ -60,31 +67,36 @@ module Rapns
60
67
  end
61
68
 
62
69
  attr_reader :app
70
+ attr_accessor :batch
63
71
 
64
72
  def initialize(app)
65
73
  @app = app
66
74
  end
67
75
 
68
- def started
69
- end
70
-
71
- def stopped
72
- end
76
+ def before_start; end
77
+ def after_start; end
78
+ def before_stop; end
79
+ def after_stop; end
73
80
 
74
81
  def start
75
- app.connections.times { handlers << start_handler }
76
- started
82
+ before_start
83
+ app.connections.times { handlers.push(start_handler) }
84
+ after_start
77
85
  Rapns.logger.info("[#{app.name}] Started, #{handlers_str}.")
78
86
  end
79
87
 
80
88
  def stop
81
- handlers.map(&:stop)
82
- stopped
83
- handlers.clear
89
+ before_stop
90
+ handlers.stop
91
+ after_stop
84
92
  end
85
93
 
86
- def enqueue(notification)
87
- queue.push(notification)
94
+ def enqueue(batch)
95
+ self.batch = batch
96
+ batch.notifications.each do |notification|
97
+ queue.push([notification, batch])
98
+ reflect(:notification_enqueued, notification)
99
+ end
88
100
  end
89
101
 
90
102
  def sync(app)
@@ -92,20 +104,20 @@ module Rapns
92
104
  diff = handlers.size - app.connections
93
105
  return if diff == 0
94
106
  if diff > 0
95
- diff.times { decrement_handlers }
96
- Rapns.logger.info("[#{app.name}] Stopped #{handlers_str(diff)}. #{handlers_str} remaining.")
107
+ decrement_handlers(diff)
108
+ Rapns.logger.info("[#{app.name}] Stopped #{handlers_str(diff)}. #{handlers_str} running.")
97
109
  else
98
- diff.abs.times { increment_handlers }
99
- Rapns.logger.info("[#{app.name}] Started #{handlers_str(diff)}. #{handlers_str} remaining.")
110
+ increment_handlers(diff.abs)
111
+ Rapns.logger.info("[#{app.name}] Started #{handlers_str(diff)}. #{handlers_str} running.")
100
112
  end
101
113
  end
102
114
 
103
- def decrement_handlers
104
- handlers.pop.stop
115
+ def decrement_handlers(num)
116
+ num.times { handlers.pop }
105
117
  end
106
118
 
107
- def increment_handlers
108
- handlers << start_handler
119
+ def increment_handlers(num)
120
+ num.times { handlers.push(start_handler) }
109
121
  end
110
122
 
111
123
  def debug
@@ -114,18 +126,28 @@ module Rapns
114
126
  #{@app.name}:
115
127
  handlers: #{num_handlers}
116
128
  queued: #{queue_size}
129
+ batch size: #{batch_size}
130
+ batch processed: #{batch_processed}
117
131
  idle: #{idle?}
118
132
  EOS
119
133
  end
120
134
 
121
135
  def idle?
122
- queue.notifications_processed?
136
+ batch ? batch.complete? : true
123
137
  end
124
138
 
125
139
  def queue_size
126
140
  queue.size
127
141
  end
128
142
 
143
+ def batch_size
144
+ batch ? batch.num_notifications : 0
145
+ end
146
+
147
+ def batch_processed
148
+ batch ? batch.num_processed : 0
149
+ end
150
+
129
151
  def num_handlers
130
152
  handlers.size
131
153
  end
@@ -140,11 +162,11 @@ module Rapns
140
162
  end
141
163
 
142
164
  def queue
143
- @queue ||= Rapns::Daemon::DeliveryQueue.new
165
+ @queue ||= Queue.new
144
166
  end
145
167
 
146
168
  def handlers
147
- @handler ||= []
169
+ @handlers ||= Rapns::Daemon::DeliveryHandlerCollection.new
148
170
  end
149
171
 
150
172
  def handlers_str(count = app.connections)