pushr-core 1.0.0.pre.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +156 -0
- data/bin/pushr +43 -0
- data/lib/generators/templates/feedback_processor.rb +33 -0
- data/lib/pushr/configuration.rb +49 -0
- data/lib/pushr/core.rb +54 -0
- data/lib/pushr/daemon.rb +116 -0
- data/lib/pushr/daemon/app.rb +71 -0
- data/lib/pushr/daemon/delivery_error.rb +19 -0
- data/lib/pushr/daemon/delivery_handler.rb +41 -0
- data/lib/pushr/daemon/feedback_handler.rb +38 -0
- data/lib/pushr/daemon/logger.rb +57 -0
- data/lib/pushr/feedback.rb +37 -0
- data/lib/pushr/message.rb +36 -0
- data/lib/pushr/redis_connection.rb +38 -0
- data/lib/pushr/version.rb +3 -0
- data/spec/lib/pushr/configuration_spec.rb +58 -0
- data/spec/lib/pushr/daemon/app_spec.rb +55 -0
- data/spec/lib/pushr/daemon/delivery_error_spec.rb +15 -0
- data/spec/lib/pushr/daemon/delivery_handler_spec.rb +48 -0
- data/spec/lib/pushr/daemon/feedback_handler_spec.rb +42 -0
- data/spec/lib/pushr/daemon/logger_spec.rb +25 -0
- data/spec/lib/pushr/daemon_spec.rb +15 -0
- data/spec/lib/pushr/feedback_spec.rb +27 -0
- data/spec/lib/pushr/message_spec.rb +33 -0
- data/spec/lib/pushr/redis_connection_spec.rb +14 -0
- data/spec/spec_helper.rb +30 -0
- data/spec/support/logger.rb +11 -0
- data/spec/support/pushr_configuration_dummy.rb +22 -0
- data/spec/support/pushr_connection_dummy.rb +22 -0
- data/spec/support/pushr_dummy.rb +16 -0
- data/spec/support/pushr_feedback_dummy.rb +11 -0
- data/spec/support/pushr_feedback_processor_dummy.rb +9 -0
- data/spec/support/pushr_invalid_configuration_dummy.rb +8 -0
- data/spec/support/pushr_message_dummy.rb +13 -0
- metadata +281 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 7c269b18e78e10769483231241fb24d5244b28d8
|
4
|
+
data.tar.gz: fc7fcd4d877e6061ac263fd97530777158b053aa
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: bee51328463ac62dd6731e8e049e230c7a0ed582ee38bf9065ed8019989c8b43267067512806c596862e44ab547f61e76a78d6de470457866982ea61780e714a
|
7
|
+
data.tar.gz: 49e84cf9943ee2293a06fd4ecc84d12a9a00580b5a4a4a21329c097084f40c854ea1249b8b02efa8a4473c88a6e6a09a3934eeee5e5da00ee9e2c3a0051ac72f
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2012 YOURNAME
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
# Pushr
|
2
|
+
|
3
|
+
[![Build Status](https://travis-ci.org/9to5/pushr-core.svg?branch=master)](https://travis-ci.org/9to5/pushr-core)
|
4
|
+
[![Code Climate](https://codeclimate.com/github/9to5/pushr-core.png)](https://codeclimate.com/github/9to5/pushr-core)
|
5
|
+
[![Coverage Status](https://coveralls.io/repos/9to5/pushr-core/badge.png)](https://coveralls.io/r/9to5/pushr-core)
|
6
|
+
|
7
|
+
## Features
|
8
|
+
|
9
|
+
* Multi-App
|
10
|
+
* Multi-Provider ([APNS](https://github.com/tompesman/push-apns), [GCM](https://github.com/tompesman/push-gcm), [C2DM](https://github.com/tompesman/push-c2dm))
|
11
|
+
* Integrated feedback processing
|
12
|
+
* Rake task to cleanup the database
|
13
|
+
* Database for storage (no external dependencies)
|
14
|
+
|
15
|
+
## Installation
|
16
|
+
|
17
|
+
Add to your `GemFile`
|
18
|
+
|
19
|
+
gem 'push-core'
|
20
|
+
|
21
|
+
and add the push provider to you Gemfile:
|
22
|
+
|
23
|
+
For __APNS__ (iOS: Apple Push Notification Services):
|
24
|
+
|
25
|
+
gem 'push-apns'
|
26
|
+
|
27
|
+
For __C2DM__ (Android: Cloud to Device Messaging, deprecated by Google, not this gem):
|
28
|
+
|
29
|
+
gem 'push-c2dm'
|
30
|
+
|
31
|
+
For __GCM__ (Android: Google Cloud Messaging):
|
32
|
+
|
33
|
+
gem 'push-gcm'
|
34
|
+
|
35
|
+
And run `bundle install` to install the gems.
|
36
|
+
|
37
|
+
To generate the migration and the configuration files run:
|
38
|
+
|
39
|
+
rails g push
|
40
|
+
bundle exec rake db:migrate
|
41
|
+
|
42
|
+
## Configuration
|
43
|
+
|
44
|
+
The configuration is in the database and you add the configuration per push provider with the console (`rails c`):
|
45
|
+
|
46
|
+
APNS ([see](https://github.com/tompesman/push-core#generating-certificates)):
|
47
|
+
```ruby
|
48
|
+
Pushr::ConfigurationApns.create(app: 'app_name', connections: 2, enabled: true,
|
49
|
+
certificate: File.read('certificate.pem'),
|
50
|
+
feedback_poll: 60,
|
51
|
+
sandbox: false)
|
52
|
+
```
|
53
|
+
|
54
|
+
The `skip_check_for_error` parameter is optional and can be set to `true` or `false`. If set to `true` the APNS service will not check for errors when sending messages. This option should be used in a production environment and improves performance. In production the errors are reported and handled by the feedback service.
|
55
|
+
|
56
|
+
C2DM ([see](https://developers.google.com/android/c2dm/)):
|
57
|
+
```ruby
|
58
|
+
Pushr::ConfigurationC2dm.create(app: 'app_name', connections: 2, enabled: true,
|
59
|
+
email: '<email address here>',
|
60
|
+
password: '<password here>')
|
61
|
+
```
|
62
|
+
|
63
|
+
GCM ([see](http://developer.android.com/guide/google/gcm/gs.html)):
|
64
|
+
```ruby
|
65
|
+
Pushr::ConfigurationGcm.create(app: 'app_name', connections: 2, enabled: true,
|
66
|
+
key: '<api key here>')
|
67
|
+
```
|
68
|
+
|
69
|
+
You can have each provider per app_name and you can have more than one app_name. Use the instructions below to generate the certificate for the APNS provider. If you only want to prepare the database with the configurations, you can set the `enabled` switch to `false`. Only enabled configurations will be used by the daemon.
|
70
|
+
|
71
|
+
### Generating Certificates for APNS
|
72
|
+
|
73
|
+
1. Open up Keychain Access and select the `Certificates` category in the sidebar.
|
74
|
+
2. Expand the disclosure arrow next to the iOS Push Services certificate you want to export.
|
75
|
+
3. Select both the certificate and private key.
|
76
|
+
4. Right click and select `Export 2 items...`.
|
77
|
+
5. Save the file as `cert.p12`, make sure the File Format is `Personal Information Exchange (p12)`.
|
78
|
+
6. If you decide to set a password for your exported certificate, please read the Configuration section below.
|
79
|
+
7. Convert the certificate to a .pem, where `<environment>` should be `development` or `production`, depending on the certificate you exported.
|
80
|
+
|
81
|
+
`openssl pkcs12 -nodes -clcerts -in cert.p12 -out <environment>.pem`
|
82
|
+
|
83
|
+
8. Move the .pem file somewhere where you can use the `File.read` to load the file in the database.
|
84
|
+
|
85
|
+
## Daemon
|
86
|
+
|
87
|
+
To start the daemon:
|
88
|
+
|
89
|
+
bundle exec push <environment> <options>
|
90
|
+
|
91
|
+
Where `<environment>` is your Rails environment and `<options>` can be:
|
92
|
+
|
93
|
+
-f, --foreground Run in the foreground. Log is not written.
|
94
|
+
-p, --pid-file PATH Path to write PID file. Relative to Rails root unless absolute.
|
95
|
+
-P, --push-poll N Frequency in seconds to check for new notifications. Default: 2.
|
96
|
+
-n, --error-notification Enables error notifications via Airbrake or Bugsnag.
|
97
|
+
-F, --feedback-poll N Frequency in seconds to check for feedback for the feedback processor. Default: 60. Use 0 to disable.
|
98
|
+
-b, --feedback-processor PATH Path to the feedback processor. Default: lib/push/feedback_processor.
|
99
|
+
-v, --version Print this version of push.
|
100
|
+
-h, --help You're looking at it.
|
101
|
+
|
102
|
+
|
103
|
+
## Sending notifications
|
104
|
+
APNS:
|
105
|
+
```ruby
|
106
|
+
Pushr::MessageApns.create(
|
107
|
+
app: 'app_name',
|
108
|
+
device: '<APNS device_token here>',
|
109
|
+
alert: 'Hello World',
|
110
|
+
sound: '1.aiff',
|
111
|
+
badge: 1,
|
112
|
+
expiry: 1.day.to_i,
|
113
|
+
attributes_for_device: {key: 'MSG'})
|
114
|
+
```
|
115
|
+
C2DM:
|
116
|
+
```ruby
|
117
|
+
Pushr::MessageC2dm.create(
|
118
|
+
app: 'app_name',
|
119
|
+
device: '<C2DM registration_id here>',
|
120
|
+
payload: { message: 'Hello World' },
|
121
|
+
collapse_key: 'MSG')
|
122
|
+
```
|
123
|
+
|
124
|
+
GCM:
|
125
|
+
```ruby
|
126
|
+
Pushr::MessageGcm.create(
|
127
|
+
app: 'app_name',
|
128
|
+
device: '<GCM registration_id here>',
|
129
|
+
payload: { message: 'Hello World' },
|
130
|
+
collapse_key: 'MSG')
|
131
|
+
```
|
132
|
+
|
133
|
+
## Feedback processing
|
134
|
+
|
135
|
+
The push providers return feedback in various ways and these are captured and stored in the `push_feedback` table. The installer installs the `lib/push/feedback_processor.rb` file which is by default called every 60 seconds. In this file you can process the feedback which is different for every application.
|
136
|
+
|
137
|
+
## Maintenance
|
138
|
+
|
139
|
+
The push-core comes with a rake task to delete all the messages and feedback of the last 7 days or by the DAYS parameter.
|
140
|
+
|
141
|
+
bundle exec rake push:clean DAYS=2
|
142
|
+
|
143
|
+
## Heroku
|
144
|
+
|
145
|
+
Push runs on Heroku with the following line in the `Procfile`.
|
146
|
+
|
147
|
+
push: bundle exec push $RACK_ENV -f
|
148
|
+
|
149
|
+
## Prerequisites
|
150
|
+
|
151
|
+
* Rails 3.2.x
|
152
|
+
* Ruby 1.9.x
|
153
|
+
|
154
|
+
## Thanks
|
155
|
+
|
156
|
+
This project started as a fork of Ian Leitch [RAPNS](https://github.com/ileitch/rapns) project. The differences between this project and RAPNS is the support for C2DM and the modularity of the push providers.
|
data/bin/pushr
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'rubygems'
|
3
|
+
require 'bundler/setup'
|
4
|
+
Bundler.require
|
5
|
+
|
6
|
+
require 'optparse'
|
7
|
+
require 'pushr/core'
|
8
|
+
require 'pushr/daemon'
|
9
|
+
|
10
|
+
config = Struct.new(:foreground, :pid_file, :error_notification, :feedback_processor, :stats_processor).new
|
11
|
+
config.foreground = false
|
12
|
+
config.error_notification = false
|
13
|
+
config.feedback_processor = nil
|
14
|
+
config.stats_processor = nil
|
15
|
+
|
16
|
+
banner = 'Usage: pushr [options]'
|
17
|
+
ARGV.options do |opts|
|
18
|
+
opts.banner = banner
|
19
|
+
opts.on('-f', '--foreground', 'Run in the foreground. Log is not written.') { config.foreground = true }
|
20
|
+
opts.on('-p PATH', '--pid-file PATH', String, 'Path to write PID file. Relative to Rails root unless absolute.') { |path| config.pid_file = path }
|
21
|
+
opts.on('-b PATH', '--feedback-processor PATH', String, "Path to the feedback processor. Default: none. Example: 'lib/pushr/feedback_processor'") { |n| config.feedback_processor = n }
|
22
|
+
opts.on('-s PATH', '--stats-processor PATH', String, "Path to the stats processor. Default: none. Example: 'lib/pushr/stats_processor'") { |n| config.stats_processor = n }
|
23
|
+
opts.on('-v', '--version', 'Print this version of pushr.') { puts "pushr #{Pushr::VERSION}"; exit }
|
24
|
+
opts.on('-h', '--help', 'You\'re looking at it.') { puts opts; exit }
|
25
|
+
opts.parse!
|
26
|
+
end
|
27
|
+
|
28
|
+
if config.pid_file && !Pathname.new(config.pid_file).absolute?
|
29
|
+
config.pid_file = File.join(Dir.pwd.root, config.pid_file)
|
30
|
+
end
|
31
|
+
|
32
|
+
if ENV['AIRBRAKE_API_KEY']
|
33
|
+
require 'airbrake'
|
34
|
+
config.error_notification = true
|
35
|
+
Airbrake.configure do |config|
|
36
|
+
config.api_key = ENV['AIRBRAKE_API_KEY']
|
37
|
+
config.host = ENV['AIRBRAKE_HOST']
|
38
|
+
config.port = ENV['AIRBRAKE_PORT'] ? ENV['AIRBRAKE_PORT'].to_i : 80
|
39
|
+
config.secure = config.port == 443
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
Pushr::Daemon.start(config)
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Pushr
|
2
|
+
class FeedbackProcessor
|
3
|
+
def initialize
|
4
|
+
# make sure you've set the RAILS_ENV variable
|
5
|
+
load 'config/environment.rb'
|
6
|
+
end
|
7
|
+
|
8
|
+
def process(feedback)
|
9
|
+
if feedback.instance_of? Pushr::FeedbackGcm
|
10
|
+
if feedback.follow_up == 'delete'
|
11
|
+
# TODO: delete gcm device
|
12
|
+
Pushr::Daemon.logger.info('[FeedbackProcessor] Pushr::FeedbackGcm delete')
|
13
|
+
elsif feedback.follow_up == 'update'
|
14
|
+
# TODO: update gcm device
|
15
|
+
# device = feedback.update_to
|
16
|
+
Pushr::Daemon.logger.info('[FeedbackProcessor] Pushr::FeedbackGcm update')
|
17
|
+
end
|
18
|
+
elsif feedback.instance_of? Pushr::FeedbackC2dm
|
19
|
+
if feedback.follow_up == 'delete'
|
20
|
+
# TODO: delete c2dm device
|
21
|
+
Pushr::Daemon.logger.info('[FeedbackProcessor] Pushr::FeedbackC2dm delete')
|
22
|
+
end
|
23
|
+
elsif feedback.instance_of? Pushr::FeedbackApns
|
24
|
+
if feedback.follow_up == 'delete'
|
25
|
+
# TODO: delete apns device
|
26
|
+
Pushr::Daemon.logger.info('[FeedbackProcessor] Pushr::FeedbackApns delete')
|
27
|
+
end
|
28
|
+
else
|
29
|
+
Pushr::Daemon.logger.info('[FeedbackProcessor] Unknown feedback type')
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Pushr
|
2
|
+
class Configuration
|
3
|
+
include ActiveModel::Validations
|
4
|
+
|
5
|
+
validates :app, presence: true
|
6
|
+
validates :connections, presence: true
|
7
|
+
validates :connections, numericality: { greater_than: 0, only_integer: true }
|
8
|
+
|
9
|
+
def initialize(attributes = {})
|
10
|
+
attributes.each do |name, value|
|
11
|
+
send("#{name}=", value)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def key
|
16
|
+
"#{app}:#{name}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def save
|
20
|
+
if valid?
|
21
|
+
Pushr::Core.redis { |conn| conn.hset('pushr:configurations', key, to_json) }
|
22
|
+
return true
|
23
|
+
else
|
24
|
+
return false
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def delete
|
29
|
+
Pushr::Core.redis { |conn| conn.hdel('pushr:configurations', key) }
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.all
|
33
|
+
configurations = Pushr::Core.redis { |conn| conn.hgetall('pushr:configurations') }
|
34
|
+
configurations.each { |key, config| configurations[key] = instantiate(config, key) }
|
35
|
+
configurations.values
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.find(key)
|
39
|
+
config = Pushr::Core.redis { |conn| conn.hget('pushr:configurations', key) }
|
40
|
+
instantiate(config, key)
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.instantiate(config, id)
|
44
|
+
hsh = ::MultiJson.load(config).merge!(id: id)
|
45
|
+
klass = hsh['type'].split('::').reduce(Object) { |a, e| a.const_get e }
|
46
|
+
klass.new(hsh)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/pushr/core.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
require 'multi_json'
|
3
|
+
require 'pushr/version'
|
4
|
+
require 'pushr/configuration'
|
5
|
+
require 'pushr/message'
|
6
|
+
require 'pushr/feedback'
|
7
|
+
require 'pushr/redis_connection'
|
8
|
+
|
9
|
+
module Pushr
|
10
|
+
module Core
|
11
|
+
NAME = 'Pushr'
|
12
|
+
DEFAULTS = {}
|
13
|
+
|
14
|
+
attr_writer :options
|
15
|
+
|
16
|
+
def self.options
|
17
|
+
@options ||= DEFAULTS.dup
|
18
|
+
end
|
19
|
+
|
20
|
+
##
|
21
|
+
# Configuration for Pushr, use like:
|
22
|
+
#
|
23
|
+
# Pushr.configure do |config|
|
24
|
+
# config.redis = { :namespace => 'myapp', :size => 1, :url => 'redis://myhost:8877/mydb' }
|
25
|
+
# end
|
26
|
+
def self.configure
|
27
|
+
yield self
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.redis(&block)
|
31
|
+
fail ArgumentError, 'requires a block' unless block
|
32
|
+
@redis ||= Pushr::RedisConnection.create
|
33
|
+
@redis.with(&block)
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.redis=(hash)
|
37
|
+
if hash.is_a?(Hash)
|
38
|
+
@redis = RedisConnection.create(hash)
|
39
|
+
options[:namespace] ||= hash[:namespace]
|
40
|
+
elsif hash.is_a?(ConnectionPool)
|
41
|
+
@redis = hash
|
42
|
+
else
|
43
|
+
fail ArgumentError, 'redis= requires a Hash or ConnectionPool'
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# instruments with a block
|
48
|
+
def self.instrument(name, payload = {}, &block)
|
49
|
+
ActiveSupport::Notifications.instrument(name, payload) do
|
50
|
+
yield
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/pushr/daemon.rb
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'logger'
|
3
|
+
require 'multi_json'
|
4
|
+
require 'pushr/redis_connection'
|
5
|
+
require 'pushr/daemon/delivery_error'
|
6
|
+
require 'pushr/daemon/delivery_handler'
|
7
|
+
require 'pushr/daemon/feedback_handler'
|
8
|
+
require 'pushr/daemon/logger'
|
9
|
+
require 'pushr/daemon/app'
|
10
|
+
|
11
|
+
module Pushr
|
12
|
+
module Daemon
|
13
|
+
class << self
|
14
|
+
attr_accessor :logger, :config, :feedback_handler
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.start(config)
|
18
|
+
self.config = config
|
19
|
+
self.logger = Logger.new(foreground: config.foreground, error_notification: config.error_notification)
|
20
|
+
setup_signal_hooks
|
21
|
+
|
22
|
+
daemonize unless config.foreground
|
23
|
+
write_pid_file
|
24
|
+
|
25
|
+
load_stats_processor
|
26
|
+
|
27
|
+
App.load
|
28
|
+
scale_redis_connections
|
29
|
+
App.start
|
30
|
+
self.feedback_handler = FeedbackHandler.new(config.feedback_processor)
|
31
|
+
feedback_handler.start
|
32
|
+
|
33
|
+
logger.info('[Daemon] Ready')
|
34
|
+
while !@shutting_down
|
35
|
+
sleep 1
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
protected
|
40
|
+
|
41
|
+
def self.scale_redis_connections
|
42
|
+
# feedback handler + app + app.totalconnections
|
43
|
+
connections = 1 + 1 + App.total_connections
|
44
|
+
Pushr::Core.configure do |config|
|
45
|
+
config.redis = { size: connections }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.load_stats_processor
|
50
|
+
if config.stats_processor
|
51
|
+
require "#{Dir.pwd}/#{config.stats_processor}"
|
52
|
+
end
|
53
|
+
rescue => e
|
54
|
+
logger.error("Failed to stats_processor: #{e.inspect}")
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.setup_signal_hooks
|
58
|
+
@shutting_down = false
|
59
|
+
|
60
|
+
%w(SIGINT SIGTERM).each do |signal|
|
61
|
+
Signal.trap(signal) do
|
62
|
+
handle_shutdown_signal
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.handle_shutdown_signal
|
68
|
+
exit 1 if @shutting_down
|
69
|
+
@shutting_down = true
|
70
|
+
shutdown
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.shutdown
|
74
|
+
print "\nShutting down..."
|
75
|
+
feedback_handler.stop
|
76
|
+
App.stop
|
77
|
+
|
78
|
+
while Thread.list.count > 1
|
79
|
+
sleep 0.1
|
80
|
+
print '.'
|
81
|
+
end
|
82
|
+
print "\n"
|
83
|
+
delete_pid_file
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.daemonize
|
87
|
+
exit if pid = fork
|
88
|
+
Process.setsid
|
89
|
+
exit if pid = fork
|
90
|
+
|
91
|
+
Dir.chdir '/'
|
92
|
+
File.umask 0000
|
93
|
+
|
94
|
+
STDIN.reopen '/dev/null'
|
95
|
+
STDOUT.reopen '/dev/null', 'a'
|
96
|
+
STDERR.reopen STDOUT
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.write_pid_file
|
100
|
+
unless config[:pid_file].blank?
|
101
|
+
begin
|
102
|
+
File.open(config[:pid_file], 'w') do |f|
|
103
|
+
f.puts $PROCESS_ID
|
104
|
+
end
|
105
|
+
rescue SystemCallError => e
|
106
|
+
logger.error("Failed to write PID to '#{config[:pid_file]}': #{e.inspect}")
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def self.delete_pid_file
|
112
|
+
pid_file = config[:pid_file]
|
113
|
+
File.delete(pid_file) if !pid_file.blank? && File.exist?(pid_file)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|