pushr-core 1.0.0.pre.1
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.
- 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
|
+
[](https://travis-ci.org/9to5/pushr-core)
|
4
|
+
[](https://codeclimate.com/github/9to5/pushr-core)
|
5
|
+
[](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
|