rapns 0.1.3 → 0.1.4

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