rpush 2.0.1 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +9 -0
  3. data/README.md +24 -18
  4. data/bin/rpush +1 -42
  5. data/lib/generators/rpush_config_generator.rb +7 -0
  6. data/lib/generators/{rpush_generator.rb → rpush_migration_generator.rb} +6 -11
  7. data/lib/generators/templates/rpush.rb +12 -12
  8. data/lib/generators/templates/rpush_2_0_0_updates.rb +2 -1
  9. data/lib/generators/templates/rpush_2_1_0_updates.rb +11 -0
  10. data/lib/rpush.rb +12 -4
  11. data/lib/rpush/apns_feedback.rb +1 -1
  12. data/lib/rpush/cli.rb +133 -0
  13. data/lib/rpush/client/active_model/apns/binary_notification_validator.rb +2 -2
  14. data/lib/rpush/client/active_model/apns/notification.rb +4 -1
  15. data/lib/rpush/client/active_model/notification.rb +0 -4
  16. data/lib/rpush/client/active_record/apns/notification.rb +0 -6
  17. data/lib/rpush/client/active_record/notification.rb +3 -2
  18. data/lib/rpush/client/redis/notification.rb +2 -0
  19. data/lib/rpush/configuration.rb +16 -24
  20. data/lib/rpush/daemon.rb +28 -1
  21. data/lib/rpush/daemon/app_runner.rb +6 -1
  22. data/lib/rpush/daemon/proc_title.rb +1 -1
  23. data/lib/rpush/daemon/signal_handler.rb +13 -3
  24. data/lib/rpush/daemon/store/active_record.rb +1 -1
  25. data/lib/rpush/deprecatable.rb +2 -1
  26. data/lib/rpush/embed.rb +1 -1
  27. data/lib/rpush/logger.rb +32 -19
  28. data/lib/rpush/push.rb +1 -1
  29. data/lib/rpush/version.rb +1 -1
  30. data/lib/tasks/quality.rake +1 -1
  31. data/lib/tasks/test.rake +12 -0
  32. data/spec/functional/apns_spec.rb +13 -11
  33. data/spec/functional_spec_helper.rb +1 -4
  34. data/spec/spec_helper.rb +4 -4
  35. data/spec/support/active_record_setup.rb +2 -1
  36. data/spec/unit/client/active_record/apns/notification_spec.rb +29 -3
  37. data/spec/unit/configuration_spec.rb +0 -7
  38. data/spec/unit/daemon/app_runner_spec.rb +1 -1
  39. data/spec/unit/daemon/signal_handler_spec.rb +1 -1
  40. data/spec/unit/daemon_spec.rb +1 -1
  41. data/spec/unit/deprecatable_spec.rb +1 -1
  42. data/spec/unit/logger_spec.rb +4 -4
  43. metadata +48 -3
@@ -3,10 +3,10 @@ module Rpush
3
3
  module ActiveModel
4
4
  module Apns
5
5
  class BinaryNotificationValidator < ::ActiveModel::Validator
6
- MAX_BYTES = 256
6
+ MAX_BYTES = 2048
7
7
 
8
8
  def validate(record)
9
- return unless record.payload_size > MAX_BYTES
9
+ return unless record.payload.bytesize > MAX_BYTES
10
10
  record.errors[:base] << "APN notification cannot be larger than #{MAX_BYTES} bytes. Try condensing your alert and device attributes."
11
11
  end
12
12
  end
@@ -48,6 +48,8 @@ module Rpush
48
48
  json['aps']['alert'] = alert if alert
49
49
  json['aps']['badge'] = badge if badge
50
50
  json['aps']['sound'] = sound if sound
51
+ json['aps']['category'] = category if category
52
+ json['aps']['url-args'] = url_args if url_args
51
53
 
52
54
  if data && data[CONTENT_AVAILABLE_KEY]
53
55
  json['aps']['content-available'] = 1
@@ -63,10 +65,11 @@ module Rpush
63
65
  end
64
66
 
65
67
  def to_binary(options = {})
68
+ frame_payload = payload
66
69
  frame_id = options[:for_validation] ? 0 : id
67
70
  frame = ""
68
71
  frame << [1, 32, device_token].pack("cnH*")
69
- frame << [2, payload.bytesize, payload].pack("cna*")
72
+ frame << [2, frame_payload.bytesize, frame_payload].pack("cna*")
70
73
  frame << [3, 4, frame_id].pack("cnN")
71
74
  frame << [4, 4, expiry || APNS_DEFAULT_EXPIRY].pack("cnN")
72
75
  frame << [5, 1, priority_for_frame].pack("cnc")
@@ -13,10 +13,6 @@ module Rpush
13
13
  multi_json_dump(as_json)
14
14
  end
15
15
 
16
- def payload_size
17
- payload.bytesize
18
- end
19
-
20
16
  def payload_data_size
21
17
  multi_json_dump(as_json['data']).bytesize
22
18
  end
@@ -6,12 +6,6 @@ module Rpush
6
6
  include Deprecatable
7
7
  include Rpush::Client::ActiveModel::Apns::Notification
8
8
 
9
- alias_method :attributes_for_device=, :data=
10
- alias_method :attributes_for_device, :data
11
-
12
- deprecated(:attributes_for_device, '2.1.0', 'Use :data instead.')
13
- deprecated(:attributes_for_device=, '2.1.0', 'Use :data instead.')
14
-
15
9
  def alert=(alert)
16
10
  if alert.is_a?(Hash)
17
11
  write_attribute(:alert, multi_json_dump(alert))
@@ -7,15 +7,16 @@ module Rpush
7
7
 
8
8
  self.table_name = 'rpush_notifications'
9
9
 
10
- # TODO: Dump using multi json.
11
10
  serialize :registration_ids
11
+ serialize :url_args
12
12
 
13
13
  belongs_to :app, class_name: 'Rpush::Client::ActiveRecord::App'
14
14
 
15
15
  if Rpush.attr_accessible_available?
16
16
  attr_accessible :badge, :device_token, :sound, :alert, :data, :expiry, :delivered,
17
17
  :delivered_at, :failed, :failed_at, :error_code, :error_description, :deliver_after,
18
- :alert_is_json, :app, :app_id, :collapse_key, :delay_while_idle, :registration_ids, :uri
18
+ :alert_is_json, :app, :app_id, :collapse_key, :delay_while_idle, :registration_ids,
19
+ :uri, :url_args, :category
19
20
  end
20
21
 
21
22
  def data=(attrs)
@@ -40,6 +40,8 @@ module Rpush
40
40
  attribute :registration_ids, :array
41
41
  attribute :uri, :string
42
42
  attribute :priority, :integer
43
+ attribute :url_args, :array
44
+ attribute :category, :string
43
45
 
44
46
  def app
45
47
  return nil unless app_id
@@ -23,9 +23,10 @@ module Rpush
23
23
  @client_initialized = true
24
24
  end
25
25
 
26
- CONFIG_ATTRS = [:foreground, :push_poll, :feedback_poll, :embedded,
27
- :check_for_errors, :pid_file, :batch_size, :push, :client, :logger,
28
- :batch_storage_updates, :log_dir, :environment]
26
+ CONFIG_ATTRS = [:push_poll, :feedback_poll, :embedded, :pid_file, :batch_size,
27
+ :push, :client, :logger, :log_file, :foreground, :log_level,
28
+ # Deprecated
29
+ :log_dir]
29
30
 
30
31
  class ConfigurationWithoutDefaults < Struct.new(*CONFIG_ATTRS)
31
32
  end
@@ -33,8 +34,7 @@ module Rpush
33
34
  class Configuration < Struct.new(*CONFIG_ATTRS)
34
35
  include Deprecatable
35
36
 
36
- deprecated(:batch_storage_updates=, '2.1.0', 'Updates are now always batched by the storage backends.')
37
- deprecated(:check_for_errors=, '2.1.0', 'APNs error detection is now performed asynchronously and does not require pauses.')
37
+ deprecated(:log_dir=, '2.3.0', 'Please use log_file instead.')
38
38
 
39
39
  delegate :redis_options, :redis_options=, to: :Modis
40
40
 
@@ -52,45 +52,37 @@ module Rpush
52
52
 
53
53
  def pid_file=(path)
54
54
  if path && !Pathname.new(path).absolute?
55
- super(File.join(Rails.root, path))
55
+ super(File.join(Rpush.root, path))
56
56
  else
57
57
  super
58
58
  end
59
59
  end
60
60
 
61
- def logger=(logger)
62
- super(logger)
63
- end
64
-
65
- def foreground=(bool)
66
- if Rpush.jruby?
67
- # The JVM does not support fork().
68
- super(true)
61
+ def log_file=(path)
62
+ if path && !Pathname.new(path).absolute?
63
+ super(File.join(Rpush.root, path))
69
64
  else
70
65
  super
71
66
  end
72
67
  end
73
68
 
74
- def set_defaults
75
- if Rpush.jruby?
76
- # The JVM does not support fork().
77
- self.foreground = true
78
- else
79
- self.foreground = false
80
- end
69
+ def logger=(logger)
70
+ super(logger)
71
+ end
81
72
 
73
+ def set_defaults
82
74
  self.push_poll = 2
83
75
  self.feedback_poll = 60
84
76
  self.batch_size = 100
85
- self.pid_file = nil
86
77
  self.client = :active_record
87
78
  self.logger = nil
88
- self.log_dir = Rails.root
79
+ self.log_file = 'log/rpush.log'
80
+ self.pid_file = 'tmp/rpush.pid'
81
+ self.log_level = (defined?(Rails) && Rails.logger) ? Rails.logger.level : ::Logger::Severity::INFO
89
82
 
90
83
  # Internal options.
91
84
  self.embedded = false
92
85
  self.push = false
93
- self.environment = Rails.env
94
86
  end
95
87
  end
96
88
  end
@@ -1,3 +1,5 @@
1
+ # encoding: UTF-8
2
+
1
3
  require 'thread'
2
4
  require 'socket'
3
5
  require 'pathname'
@@ -46,6 +48,8 @@ require 'rpush/daemon/adm'
46
48
 
47
49
  module Rpush
48
50
  module Daemon
51
+ extend Term::ANSIColor
52
+
49
53
  class << self
50
54
  attr_accessor :store
51
55
  end
@@ -60,6 +64,9 @@ module Rpush
60
64
  # No further store connections will be made from this thread.
61
65
  store.release_connection
62
66
 
67
+ Rpush.logger.info('Rpush operational.')
68
+ show_welcome_if_needed
69
+
63
70
  # Blocking call, returns after Feeder.stop is called from another thread.
64
71
  Feeder.start
65
72
 
@@ -68,12 +75,19 @@ module Rpush
68
75
  end
69
76
 
70
77
  def self.shutdown
71
- puts "\nShutting down..."
78
+ if Rpush.config.foreground
79
+ # Eat the '^C'
80
+ STDOUT.write("\b\b")
81
+ STDOUT.flush
82
+ end
83
+
84
+ Rpush.logger.info('Shutting down... ', true)
72
85
 
73
86
  shutdown_lock.synchronize do
74
87
  Feeder.stop
75
88
  AppRunner.stop
76
89
  delete_pid_file
90
+ puts green('✔') if Rpush.config.foreground
77
91
  end
78
92
  end
79
93
 
@@ -105,6 +119,7 @@ module Rpush
105
119
  def self.write_pid_file
106
120
  unless Rpush.config.pid_file.blank?
107
121
  begin
122
+ FileUtils.mkdir_p(File.dirname(Rpush.config.pid_file))
108
123
  File.open(Rpush.config.pid_file, 'w') { |f| f.puts Process.pid }
109
124
  rescue SystemCallError => e
110
125
  Rpush.logger.error("Failed to write PID to '#{Rpush.config.pid_file}': #{e.inspect}")
@@ -116,5 +131,17 @@ module Rpush
116
131
  pid_file = Rpush.config.pid_file
117
132
  File.delete(pid_file) if !pid_file.blank? && File.exist?(pid_file)
118
133
  end
134
+
135
+ def self.show_welcome_if_needed
136
+ if Rpush::Daemon::AppRunner.app_ids.count == 0
137
+ puts <<-EOS
138
+
139
+ * #{green('Is this your first time using Rpush?')}
140
+ You need to create an App before you can start using Rpush.
141
+ Please refer to the documentation at https://github.com/rpush/rpush
142
+
143
+ EOS
144
+ end
145
+ end
119
146
  end
120
147
  end
@@ -1,6 +1,10 @@
1
+ # encoding: UTF-8
2
+
1
3
  module Rpush
2
4
  module Daemon
3
5
  class AppRunner
6
+ extend Term::ANSIColor
7
+
4
8
  extend Reflectable
5
9
  include Reflectable
6
10
  include Loggable
@@ -24,9 +28,10 @@ module Rpush
24
28
  end
25
29
 
26
30
  def self.start_app(app)
31
+ Rpush.logger.info("[#{app.name}] Starting #{pluralize(app.connections, 'dispatcher')}... ", true)
27
32
  @runners[app.id] = new(app)
28
33
  @runners[app.id].start
29
- log_info("[#{app.name}] Started, #{pluralize(app.connections, 'dispatcher')}.")
34
+ puts green('') if Rpush.config.foreground
30
35
  rescue StandardError => e
31
36
  @runners.delete(app.id)
32
37
  Rpush.logger.error("[#{app.name}] Exception raised during startup. Notifications will not be delivered for this app.")
@@ -9,7 +9,7 @@ module Rpush
9
9
  total_dispatchers = AppRunner.total_dispatchers
10
10
  dispatchers_str = total_dispatchers == 1 ? 'dispatcher' : 'dispatchers'
11
11
  total_queued = AppRunner.total_queued
12
- format("rpush | %s | %d queued | %d %s", Rpush.config.environment, total_queued, total_dispatchers, dispatchers_str)
12
+ format("rpush | %d queued | %d %s", total_queued, total_dispatchers, dispatchers_str)
13
13
  end
14
14
  end
15
15
  end
@@ -28,10 +28,9 @@ module Rpush
28
28
  begin
29
29
  case signal
30
30
  when 'HUP'
31
- Synchronizer.sync
32
- Feeder.wakeup
31
+ handle_hup
33
32
  when 'USR2'
34
- AppRunner.debug
33
+ handle_usr2
35
34
  when 'INT', 'TERM'
36
35
  Thread.new { Rpush::Daemon.shutdown }
37
36
  break
@@ -48,6 +47,17 @@ module Rpush
48
47
  end
49
48
  end
50
49
 
50
+ def self.handle_hup
51
+ Rpush.logger.info("Received HUP signal.")
52
+ Synchronizer.sync
53
+ Feeder.wakeup
54
+ end
55
+
56
+ def self.handle_usr2
57
+ Rpush.logger.info("Received USR2 signal.")
58
+ AppRunner.debug
59
+ end
60
+
51
61
  def self.trap_signals?
52
62
  !Rpush.config.embedded
53
63
  end
@@ -166,7 +166,7 @@ module Rpush
166
166
  end
167
167
 
168
168
  def ready_for_delivery
169
- Rpush::Client::ActiveRecord::Notification.where('processing = ? AND delivered = ? AND failed = ? AND (deliver_after IS NULL OR deliver_after < ?)', false, false, false, Time.now)
169
+ Rpush::Client::ActiveRecord::Notification.where('processing = ? AND delivered = ? AND failed = ? AND (deliver_after IS NULL OR deliver_after < ?)', false, false, false, Time.now).order('created_at ASC')
170
170
  end
171
171
 
172
172
  def mark_processing(notifications)
@@ -14,7 +14,8 @@ module Rpush
14
14
  warning << " #{msg}" if msg
15
15
  class_eval(<<-RUBY, __FILE__, __LINE__)
16
16
  def #{method_name}(*args, &blk)
17
- Rpush::Deprecation.warn(#{warning.inspect})
17
+ trace = "\n\nCALLED FROM:\n" + caller.join("\n")
18
+ Rpush::Deprecation.warn(#{warning.inspect} + trace)
18
19
  #{method_name_as_var}_without_warning(*args, &blk)
19
20
  end
20
21
  RUBY
@@ -1,6 +1,6 @@
1
1
  module Rpush
2
2
  def self.embed(options = {})
3
- Rpush.require_for_daemon
3
+ require 'rpush/daemon'
4
4
 
5
5
  if @embed_thread
6
6
  STDERR.puts 'Rpush.embed can only be run once inside this process.'
@@ -1,9 +1,8 @@
1
1
  module Rpush
2
2
  class Logger
3
3
  def initialize
4
- log_dir = File.join(Rpush.config.log_dir, 'log')
5
- FileUtils.mkdir_p(log_dir)
6
- log = File.open(File.join(log_dir, 'rpush.log'), 'a')
4
+ FileUtils.mkdir_p(File.dirname(Rpush.config.log_file))
5
+ log = File.open(Rpush.config.log_file, 'a')
7
6
  log.sync = true
8
7
  setup_logger(log)
9
8
  rescue Errno::ENOENT, Errno::EPERM => e
@@ -12,16 +11,16 @@ module Rpush
12
11
  error('Logging disabled.')
13
12
  end
14
13
 
15
- def info(msg)
16
- log(:info, msg)
14
+ def info(msg, inline = false)
15
+ log(:info, msg, inline)
17
16
  end
18
17
 
19
- def error(msg)
20
- log(:error, msg, 'ERROR', STDERR)
18
+ def error(msg, inline = false)
19
+ log(:error, msg, inline, 'ERROR', STDERR)
21
20
  end
22
21
 
23
- def warn(msg)
24
- log(:warn, msg, 'WARNING', STDERR)
22
+ def warn(msg, inline = false)
23
+ log(:warn, msg, inline, 'WARNING', STDERR)
25
24
  end
26
25
 
27
26
  private
@@ -30,14 +29,22 @@ module Rpush
30
29
  if Rpush.config.logger
31
30
  @logger = Rpush.config.logger
32
31
  elsif ActiveSupport.const_defined?('BufferedLogger')
33
- @logger = ActiveSupport::BufferedLogger.new(log, Rails.logger.level)
34
- @logger.auto_flushing = Rails.logger.respond_to?(:auto_flushing) ? Rails.logger.auto_flushing : true
32
+ @logger = ActiveSupport::BufferedLogger.new(log, Rpush.config.log_level)
33
+ @logger.auto_flushing = auto_flushing
35
34
  else
36
- @logger = ActiveSupport::Logger.new(log, Rails.logger.level)
35
+ @logger = ActiveSupport::Logger.new(log, Rpush.config.log_level)
37
36
  end
38
37
  end
39
38
 
40
- def log(where, msg, prefix = nil, io = STDOUT)
39
+ def auto_flushing
40
+ if defined?(Rails) && Rails.logger.respond_to?(:auto_flushing)
41
+ Rails.logger.auto_flushing
42
+ else
43
+ true
44
+ end
45
+ end
46
+
47
+ def log(where, msg, inline = false, prefix = nil, io = STDOUT)
41
48
  if msg.is_a?(Exception)
42
49
  formatted_backtrace = msg.backtrace.join("\n")
43
50
  msg = "#{msg.class.name}, #{msg.message}\n#{formatted_backtrace}"
@@ -47,13 +54,19 @@ module Rpush
47
54
  formatted_msg << "[#{prefix}] " if prefix
48
55
  formatted_msg << msg
49
56
 
50
- if io == STDERR
51
- io.puts formatted_msg
52
- elsif Rpush.config.foreground
53
- io.puts formatted_msg
54
- end
55
-
57
+ log_foreground(io, formatted_msg, inline)
56
58
  @logger.send(where, formatted_msg) if @logger
57
59
  end
60
+
61
+ def log_foreground(io, formatted_msg, inline)
62
+ return unless io == STDERR || Rpush.config.foreground
63
+
64
+ if inline
65
+ io.write(formatted_msg)
66
+ io.flush
67
+ else
68
+ io.puts(formatted_msg)
69
+ end
70
+ end
58
71
  end
59
72
  end
@@ -1,6 +1,6 @@
1
1
  module Rpush
2
2
  def self.push(options = {})
3
- Rpush.require_for_daemon
3
+ require 'rpush/daemon'
4
4
 
5
5
  config = Rpush::ConfigurationWithoutDefaults.new
6
6
  options.each { |k, v| config.send("#{k}=", v) }
@@ -1,3 +1,3 @@
1
1
  module Rpush
2
- VERSION = '2.0.1'
2
+ VERSION = '2.1.0'
3
3
  end
@@ -7,7 +7,7 @@ begin
7
7
  cane.no_style = false
8
8
  cane.style_measure = 1000
9
9
  cane.no_doc = true
10
- cane.abc_max = 20
10
+ cane.abc_max = 22
11
11
  end
12
12
 
13
13
  namespace :spec do
@@ -21,8 +21,20 @@ namespace :test do
21
21
  begin
22
22
  Dir.chdir(path)
23
23
  cmd('echo "gem \'rake\'" >> Gemfile')
24
+ cmd('echo "gem \'pg\'" >> Gemfile')
24
25
  cmd("echo \"gem 'rpush', :path => '#{rpush_root}'\" >> Gemfile")
25
26
  cmd('bundle install')
27
+
28
+ File.open('config/database.yml', 'w') do |fd|
29
+ fd.write(<<-YML)
30
+ development:
31
+ adapter: postgresql
32
+ database: rpush_rails_test
33
+ pool: 5
34
+ timeout: 5000
35
+ YML
36
+ end
37
+
26
38
  cmd('bundle exec rails g rpush')
27
39
  cmd('bundle exec rake db:migrate')
28
40
  ensure