rapns 0.1.3 → 0.1.4

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.
data/README.md CHANGED
@@ -50,6 +50,7 @@ If you want to use rapns in environments other than development or production, y
50
50
  * `airbrake_notify` (default: true) Enables/disables error notifications via Airbrake.
51
51
  * `poll` (default: 2) Frequency in seconds to check for new notifications to deliver.
52
52
  * `connections` (default: 3) the number of connections to keep open to the APNs. Consider increasing this if you are sending a very large number of notifications.
53
+ * `pid_file` (default: blank) the file that rapns will write its process ID to. Paths are relative to your project's RAILS_ROOT unless an absolute path is given.
53
54
 
54
55
  ## Starting the rapns Daemon
55
56
 
@@ -14,4 +14,5 @@ production:
14
14
  certificate_password:
15
15
  airbrake_notify: true
16
16
  poll: 2
17
- connections: 3
17
+ connections: 3
18
+ pid_file: tmp/pids/rapns.pid
@@ -5,7 +5,7 @@ module Rapns
5
5
 
6
6
  module Daemon
7
7
  class Configuration
8
- attr_accessor :host, :port, :certificate, :certificate_password, :poll, :airbrake_notify, :connections
8
+ attr_accessor :host, :port, :certificate, :certificate_password, :poll, :airbrake_notify, :connections, :pid_file
9
9
  alias_method :airbrake_notify?, :airbrake_notify
10
10
 
11
11
  def initialize(environment, config_path)
@@ -24,6 +24,7 @@ module Rapns
24
24
  set_variable(:certificate_password, config, :optional => true, :default => "")
25
25
  set_variable(:poll, config, :optional => true, :default => 2)
26
26
  set_variable(:connections, config, :optional => true, :default => 3)
27
+ set_variable(:pid_file, config, :optional => true, :default => "")
27
28
  end
28
29
 
29
30
  def certificate
@@ -34,6 +35,16 @@ module Rapns
34
35
  end
35
36
  end
36
37
 
38
+ def pid_file
39
+ return if @pid_file.blank?
40
+
41
+ if Pathname.new(@pid_file).absolute?
42
+ @pid_file
43
+ else
44
+ File.join(Rails.root, @pid_file)
45
+ end
46
+ end
47
+
37
48
  protected
38
49
 
39
50
  def read_config
@@ -40,7 +40,7 @@ module Rapns
40
40
 
41
41
  check_for_error
42
42
  rescue Errno::EPIPE => e
43
- Rapns::Daemon.logger.warn("[#{@name}] Lost connection to #{Rapns::Daemon.configuration.host}:#{Rapns::Daemon.configuration.port}, reconnecting...")
43
+ Rapns::Daemon.logger.error("[#{@name}] Lost connection to #{Rapns::Daemon.configuration.host}:#{Rapns::Daemon.configuration.port}, reconnecting...")
44
44
  @tcp_socket, @ssl_socket = connect_socket
45
45
 
46
46
  retry_count += 1
@@ -70,7 +70,7 @@ module Rapns
70
70
  end
71
71
 
72
72
  begin
73
- Rapns::Daemon.logger.warn("[#{@name}] Error received, reconnecting...")
73
+ Rapns::Daemon.logger.error("[#{@name}] Error received, reconnecting...")
74
74
  close
75
75
  @tcp_socket, @ssl_socket = connect_socket
76
76
  ensure
@@ -47,7 +47,7 @@ module Rapns
47
47
  rescue StandardError => e
48
48
  Rapns::Daemon.logger.error(e)
49
49
  ensure
50
- Rapns::Daemon.delivery_queue.signal
50
+ Rapns::Daemon.delivery_queue.signal_waiters_if_empty
51
51
  end
52
52
  end
53
53
  end
@@ -5,11 +5,9 @@ module Rapns
5
5
  @queue = Queue.new
6
6
  @waiting_threads = []
7
7
  @mutex = Mutex.new
8
- @counter = 0
9
8
  end
10
9
 
11
10
  def push(obj)
12
- @mutex.synchronize { @counter += 1 }
13
11
  @queue.push(obj)
14
12
  end
15
13
 
@@ -17,11 +15,10 @@ module Rapns
17
15
  @queue.pop
18
16
  end
19
17
 
20
- def signal
18
+ def signal_waiters_if_empty
21
19
  @mutex.synchronize do
22
20
  begin
23
- @counter -= 1
24
- if @counter <= 0
21
+ if @queue.size == 0
25
22
  t = @waiting_threads.shift
26
23
  t.wakeup if t
27
24
  end
@@ -31,9 +28,9 @@ module Rapns
31
28
  end
32
29
  end
33
30
 
34
- def wait
31
+ def wait_until_empty
35
32
  Thread.exclusive do
36
- if @counter > 0
33
+ if @queue.size > 0
37
34
  @waiting_threads << Thread.current
38
35
  Thread.stop
39
36
  end
@@ -14,7 +14,7 @@ module Rapns
14
14
  Rapns::Daemon.delivery_queue.push(notification)
15
15
  end
16
16
 
17
- Rapns::Daemon.delivery_queue.wait
17
+ Rapns::Daemon.delivery_queue.wait_until_empty
18
18
  rescue StandardError => e
19
19
  Rapns::Daemon.logger.error(e)
20
20
  end
data/lib/rapns/daemon.rb CHANGED
@@ -1,3 +1,7 @@
1
+ require 'thread'
2
+ require 'socket'
3
+ require 'pathname'
4
+
1
5
  require "rapns/daemon/configuration"
2
6
  require "rapns/daemon/certificate"
3
7
  require "rapns/daemon/delivery_error"
@@ -37,6 +41,8 @@ module Rapns
37
41
  ActiveRecord::Base.establish_connection
38
42
  end
39
43
 
44
+ write_pid_file
45
+
40
46
  self.delivery_handler_pool = DeliveryHandlerPool.new(configuration.connections)
41
47
  delivery_handler_pool.populate
42
48
 
@@ -49,19 +55,27 @@ module Rapns
49
55
  protected
50
56
 
51
57
  def self.setup_signal_hooks
52
- @sigint_received = false
53
- Signal.trap("SIGINT") do
54
- exit 1 if @sigint_received
55
- @sigint_received = true
56
- shutdown
58
+ @shutting_down = false
59
+
60
+ ["SIGINT", "SIGTERM"].each do |signal|
61
+ Signal.trap(signal) do
62
+ handle_shutdown_signal
63
+ end
57
64
  end
58
65
  end
59
66
 
67
+ def self.handle_shutdown_signal
68
+ exit 1 if @shutting_down
69
+ @shutting_down = true
70
+ shutdown
71
+ end
72
+
60
73
  def self.shutdown
61
74
  puts "\nShutting down..."
62
75
  Rapns::Daemon::Feeder.stop
63
76
  Rapns::Daemon.delivery_handler_pool.drain if Rapns::Daemon.delivery_handler_pool
64
77
  Rapns::Daemon.connection_pool.drain if Rapns::Daemon.connection_pool
78
+ delete_pid_file
65
79
  end
66
80
 
67
81
  def self.daemonize
@@ -76,5 +90,18 @@ module Rapns
76
90
  STDOUT.reopen '/dev/null', 'a'
77
91
  STDERR.reopen STDOUT
78
92
  end
93
+
94
+ def self.write_pid_file
95
+ if !configuration.pid_file.blank?
96
+ File.open(configuration.pid_file, "w") do |f|
97
+ f.puts $$
98
+ end
99
+ end
100
+ end
101
+
102
+ def self.delete_pid_file
103
+ pid_file = configuration.pid_file
104
+ File.delete(pid_file) if !pid_file.blank? && File.exists?(pid_file)
105
+ end
79
106
  end
80
107
  end
data/lib/rapns/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Rapns
2
- VERSION = "0.1.3"
2
+ VERSION = '0.1.4'
3
3
  end
data/lib/rapns.rb CHANGED
@@ -1,5 +1,5 @@
1
- require "active_record"
1
+ require 'active_record'
2
2
 
3
- require "rapns/version"
4
- require "rapns/binary_notification_validator"
5
- require "rapns/notification"
3
+ require 'rapns/version'
4
+ require 'rapns/binary_notification_validator'
5
+ require 'rapns/notification'
@@ -5,7 +5,8 @@ describe Rapns::Daemon::Configuration do
5
5
  end
6
6
 
7
7
  before do
8
- @config = {"port" => 123, "host" => "localhost", "certificate" => "production.pem", "certificate_password" => "abc123", "airbrake_notify" => false, "poll" => 4, "connections" => 6}
8
+ Rails.stub(:root).and_return("/rails_root")
9
+ @config = {"port" => 123, "host" => "localhost", "certificate" => "production.pem", "certificate_password" => "abc123", "airbrake_notify" => false, "poll" => 4, "connections" => 6, "pid_file" => "rapns.pid"}
9
10
  end
10
11
 
11
12
  it "should raise an error if the configuration file does not exist" do
@@ -107,7 +108,6 @@ describe Rapns::Daemon::Configuration do
107
108
  end
108
109
 
109
110
  it "should set the certificate, with absolute path" do
110
- Rails.stub(:root).and_return("/rails_root")
111
111
  configuration = Rapns::Daemon::Configuration.new("production", "/some/config.yml")
112
112
  configuration.stub(:read_config).and_return({"production" => @config})
113
113
  configuration.load
@@ -115,11 +115,33 @@ describe Rapns::Daemon::Configuration do
115
115
  end
116
116
 
117
117
  it "should keep the absolute path of the certificate if it has one" do
118
- Rails.stub(:root).and_return("/rails_root")
119
118
  @config["certificate"] = "/different_path/to/production.pem"
120
119
  configuration = Rapns::Daemon::Configuration.new("production", "/some/config.yml")
121
120
  configuration.stub(:read_config).and_return({"production" => @config})
122
121
  configuration.load
123
122
  configuration.certificate.should == "/different_path/to/production.pem"
124
123
  end
124
+
125
+ it "should set the PID file path" do
126
+ configuration = Rapns::Daemon::Configuration.new("production", "/some/config.yml")
127
+ configuration.stub(:read_config).and_return({"production" => @config})
128
+ configuration.load
129
+ configuration.pid_file.should == "/rails_root/rapns.pid"
130
+ end
131
+
132
+ it "should keep the absolute path of the PID file if it has one" do
133
+ @config["pid_file"] = "/some/absolue/path/rapns.pid"
134
+ configuration = Rapns::Daemon::Configuration.new("production", "/some/config.yml")
135
+ configuration.stub(:read_config).and_return({"production" => @config})
136
+ configuration.load
137
+ configuration.pid_file.should == "/some/absolue/path/rapns.pid"
138
+ end
139
+
140
+ it "should return nil if no PID file was set" do
141
+ @config["pid_file"] = ""
142
+ configuration = Rapns::Daemon::Configuration.new("production", "/some/config.yml")
143
+ configuration.stub(:read_config).and_return({"production" => @config})
144
+ configuration.load
145
+ configuration.pid_file.should be_nil
146
+ end
125
147
  end
@@ -122,15 +122,15 @@ describe Rapns::Daemon::Connection, "when the connection is lost" do
122
122
  @connection.instance_variable_set("@ssl_socket", @ssl_socket)
123
123
  @connection.stub(:connect_socket).and_return([mock("TCPSocket"), @ssl_socket])
124
124
  @ssl_socket.stub(:write).and_raise(Errno::EPIPE)
125
- @logger = mock("Logger", :warn => nil)
125
+ @logger = mock("Logger", :error => nil)
126
126
  Rapns::Daemon.stub(:logger).and_return(@logger)
127
127
  @connection.stub(:sleep)
128
128
  configuration = mock("Configuration", :host => "localhost", :port => 123)
129
129
  Rapns::Daemon.stub(:configuration).and_return(configuration)
130
130
  end
131
131
 
132
- it "should log a warning" do
133
- Rapns::Daemon.logger.should_receive(:warn).with("[Connection 1] Lost connection to localhost:123, reconnecting...")
132
+ it "should log a error" do
133
+ Rapns::Daemon.logger.should_receive(:error).with("[Connection 1] Lost connection to localhost:123, reconnecting...")
134
134
  begin
135
135
  @connection.write(nil)
136
136
  rescue Rapns::Daemon::ConnectionError
@@ -248,7 +248,7 @@ describe Rapns::Daemon::Connection, "when receiving an error packet" do
248
248
  end
249
249
 
250
250
  it "should log that the connection is being reconnected" do
251
- Rapns::Daemon.logger.should_receive(:warn).with("[Connection 1] Error received, reconnecting...")
251
+ Rapns::Daemon.logger.should_receive(:error).with("[Connection 1] Error received, reconnecting...")
252
252
  begin
253
253
  @connection.write("msg with an error")
254
254
  rescue Rapns::DeliveryError
@@ -59,6 +59,11 @@ describe Rapns::Daemon::DeliveryHandler do
59
59
  @delivery_handler.send(:handle_next_notification)
60
60
  end
61
61
 
62
+ it "should signal the feeder if the delivery queue is empty" do
63
+ Rapns::Daemon.delivery_queue.should_receive(:signal_waiters_if_empty)
64
+ @delivery_handler.send(:handle_next_notification)
65
+ end
66
+
62
67
  describe "when delivery fails" do
63
68
  before do
64
69
  @error = Rapns::DeliveryError.new(4, "Missing payload", 1)
@@ -6,7 +6,7 @@ describe Rapns::Daemon::Feeder do
6
6
  @notification = Rapns::Notification.create!(:device_token => "a" * 64)
7
7
  @logger = mock("Logger", :info => nil, :error => nil)
8
8
  Rapns::Daemon.stub(:logger).and_return(@logger)
9
- @queue = mock(:push => nil, :wait => nil)
9
+ @queue = mock(:push => nil, :wait_until_empty => nil)
10
10
  Rapns::Daemon.stub(:delivery_queue).and_return(@queue)
11
11
  Rapns::Daemon.stub(:configuration => mock("Configuration", :poll => 2))
12
12
  end
@@ -53,7 +53,7 @@ describe Rapns::Daemon::Feeder do
53
53
  end
54
54
 
55
55
  it "should wait for the delivery queue to be emptied" do
56
- Rapns::Daemon.delivery_queue.should_receive(:wait)
56
+ Rapns::Daemon.delivery_queue.should_receive(:wait_until_empty)
57
57
  Rapns::Daemon::Feeder.enqueue_notifications
58
58
  end
59
59
 
@@ -8,7 +8,7 @@ describe Rapns::Daemon, "when starting" do
8
8
  Rails.stub(:root).and_return("/rails_root")
9
9
 
10
10
  @configuration = Rapns::Daemon::Configuration.new("development", "/rails_root/config/rapns/rapns.yml")
11
- @configuration.stub(:read_config).and_return({"development" => {"port" => 123, "host" => "localhost", "certificate" => "development.pem", "certificate_password" => "abc123"}})
11
+ @configuration.stub(:read_config).and_return({"development" => {"port" => 123, "host" => "localhost", "certificate" => "development.pem", "certificate_password" => "abc123", "pid_file" => "rapns.pid"}})
12
12
  Rapns::Daemon::Configuration.stub(:new).and_return(@configuration)
13
13
 
14
14
  @certificate = Rapns::Daemon::Certificate.new("/rails_root/config/rapns/development.pem")
@@ -26,6 +26,7 @@ describe Rapns::Daemon, "when starting" do
26
26
  Rapns::Daemon::Feeder.stub(:start)
27
27
  Rapns::Daemon::Feeder.stub(:wait)
28
28
  Rapns::Daemon.stub(:daemonize)
29
+ Rapns::Daemon.stub(:write_pid_file)
29
30
  @logger = mock("Logger")
30
31
  Rapns::Daemon::Logger.stub(:new).and_return(@logger)
31
32
  end
@@ -92,6 +93,11 @@ describe Rapns::Daemon, "when starting" do
92
93
  Rapns::Daemon.start("development", true)
93
94
  end
94
95
 
96
+ it "should write the process ID to the PID file" do
97
+ Rapns::Daemon.should_receive(:write_pid_file)
98
+ Rapns::Daemon.start("development", {})
99
+ end
100
+
95
101
  it "should start the feeder" do
96
102
  Rapns::Daemon::Feeder.should_receive(:start)
97
103
  Rapns::Daemon.start("development", true)
@@ -111,11 +117,14 @@ end
111
117
 
112
118
  describe Rapns::Daemon, "when being shutdown" do
113
119
  before do
120
+ Rails.stub(:root).and_return("/rails_root")
114
121
  Rapns::Daemon::Feeder.stub(:stop)
115
122
  @connection_pool = mock("ConnectionPool", :drain => nil)
116
123
  Rapns::Daemon.stub(:connection_pool).and_return(@connection_pool)
117
124
  @handler_pool = mock("DeliveryHandlerPool", :drain => nil)
118
125
  Rapns::Daemon.stub(:delivery_handler_pool).and_return(@handler_pool)
126
+ @configuration = mock("Configuration", :pid_file => File.join(Rails.root, "rapns.pid"))
127
+ Rapns::Daemon.stub(:configuration).and_return(@configuration)
119
128
  Rapns::Daemon.stub(:puts)
120
129
  end
121
130
 
@@ -145,4 +154,22 @@ describe Rapns::Daemon, "when being shutdown" do
145
154
  @handler_pool.should_not_receive(:drain)
146
155
  Rapns::Daemon.send(:shutdown)
147
156
  end
157
+
158
+ it "should remove the PID file if one was written" do
159
+ File.stub(:exists?).and_return(true)
160
+ File.should_receive(:delete).with("/rails_root/rapns.pid")
161
+ Rapns::Daemon.send(:shutdown)
162
+ end
163
+
164
+ it "should not attempt to remove the PID file if it does not exist" do
165
+ File.stub(:exists?).and_return(false)
166
+ File.should_not_receive(:delete)
167
+ Rapns::Daemon.send(:shutdown)
168
+ end
169
+
170
+ it "should not remove the PID file if one was not written" do
171
+ @configuration.stub(:pid_file).and_return(nil)
172
+ File.should_not_receive(:delete)
173
+ Rapns::Daemon.send(:shutdown)
174
+ end
148
175
  end
data/spec/spec_helper.rb CHANGED
@@ -1,5 +1,10 @@
1
1
  ENV["RAILS_ENV"] = "test"
2
2
 
3
+ require 'simplecov'
4
+ SimpleCov.start do
5
+ add_filter '/spec/'
6
+ end
7
+
3
8
  require "active_record"
4
9
 
5
10
  ActiveRecord::Base.establish_connection("adapter" => "postgresql", "database" => "rapns_test")
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rapns
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-09-01 00:00:00.000000000Z
12
+ date: 2011-10-08 00:00:00.000000000Z
13
13
  dependencies: []
14
14
  description: Easy to use library for Apple's Push Notification Service with Rails
15
15
  3
@@ -53,7 +53,7 @@ files:
53
53
  - spec/rapns/notification_spec.rb
54
54
  - spec/spec_helper.rb
55
55
  - bin/rapns
56
- homepage: ''
56
+ homepage: https://github.com/ileitch/rapns
57
57
  licenses: []
58
58
  post_install_message:
59
59
  rdoc_options: []
@@ -73,7 +73,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
73
73
  version: '0'
74
74
  requirements: []
75
75
  rubyforge_project:
76
- rubygems_version: 1.8.6
76
+ rubygems_version: 1.8.10
77
77
  signing_key:
78
78
  specification_version: 3
79
79
  summary: Easy to use library for Apple's Push Notification Service with Rails 3