tootsie 0.9.3 → 0.9.4

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -180,7 +180,6 @@ Example S3 URLs:
180
180
  Current limitations
181
181
  ===================
182
182
 
183
- * Daemon supports only one task manager thread at a time.
184
183
  * Transcoding options are very basic.
185
184
  * No client access control; anyone can submit jobs.
186
185
 
@@ -216,8 +215,11 @@ Create a configuration, eg. `tootsie.conf`:
216
215
  aws_access_key_id: <your Amazon key>
217
216
  aws_secret_access_key: <your Amazon secret>
218
217
  sqs_queue_name: tootsie
218
+ pid_path: <where to write pid file>
219
+ log_path: <where to write log file>
220
+ worker_count: <number of workers>
219
221
 
220
- Start the task manager with `tootsie -c tootsie.conf start`. This will start Tootsie as a daemon.
222
+ Start the task manager with `tootsie -c tootsie.conf`.
221
223
 
222
224
  To run the web service, you will need a Rack-compatible web server, such as Unicorn or Thin. To start with Thin on port 9090:
223
225
 
@@ -3,72 +3,4 @@
3
3
  $:.unshift(File.expand_path('../../lib', __FILE__))
4
4
  require 'tootsie'
5
5
 
6
- num_workers = 4
7
- config_path = nil
8
- log_target = 'syslog'
9
-
10
- ARGV.options do |opts|
11
- opts.banner = "Usage: #{File.basename($0)} [OPTIONS] [start | stop | restart | status]"
12
- opts.separator ""
13
- opts.on("-n", "--num-workers=WORKERS", Integer,
14
- "Specify number of workers to fork (defaults to #{num_workers}.") do |value|
15
- num_workers = [1, value.to_i].max
16
- end
17
- opts.on("-c", "--config=PATH", String,
18
- "Specify configuration path.") do |value|
19
- config_path = value
20
- end
21
- opts.on("-l TARGET", "--logger TARGET", String,
22
- "Log to TARGET, which is either a file name or 'syslog'.") do |value|
23
- log_target = value
24
- end
25
- opts.on("-h", "--help", "Show this help message.") do
26
- puts opts
27
- exit
28
- end
29
- opts.parse!
30
- if ARGV.empty?
31
- puts "Nothing to do. Run with -h for help."
32
- exit
33
- end
34
- end
35
-
36
- unless config_path
37
- abort 'Configuration file not specified.'
38
- end
39
- config_path = File.expand_path(config_path)
40
-
41
- case log_target
42
- when 'syslog'
43
- logger = SyslogLogger.new('tootsie')
44
- else
45
- logger = Logger.new(log_target)
46
- end
47
-
48
- controller = Tootsie::Daemon.new(
49
- :root => File.join(File.dirname(__FILE__), "/.."),
50
- :pid_file => File.join(File.dirname(__FILE__), "/../tmp/task_manager.pid"),
51
- :logger => logger)
52
-
53
- spawner = Spawner.new(:num_children => num_workers, :logger => controller.logger)
54
-
55
- controller.on_spawn do
56
- $0 = "tootsie: master"
57
- spawner.on_spawn do
58
- $0 = "tootsie: worker"
59
- Signal.trap('TERM') do
60
- exit(2)
61
- end
62
- app = Tootsie::Application.new(:logger => controller.logger)
63
- app.configure!(config_path)
64
- begin
65
- app.task_manager.run!
66
- rescue SystemExit, Interrupt
67
- end
68
- end
69
- spawner.run
70
- end
71
- controller.on_terminate do
72
- spawner.terminate
73
- end
74
- controller.control(ARGV)
6
+ Tootsie::Runner.new.run!(ARGV)
@@ -8,7 +8,6 @@ require 'tootsie/application'
8
8
  require 'tootsie/client'
9
9
  require 'tootsie/configuration'
10
10
  require 'tootsie/command_runner'
11
- require 'tootsie/daemon'
12
11
  require 'tootsie/spawner'
13
12
  require 'tootsie/ffmpeg_adapter'
14
13
  require 'tootsie/image_metadata_extractor'
@@ -23,3 +22,4 @@ require 'tootsie/processors/image_processor'
23
22
  require 'tootsie/queues/sqs_queue'
24
23
  require 'tootsie/queues/file_system_queue'
25
24
  require 'tootsie/s3_utilities'
25
+ require 'tootsie/runner'
@@ -4,12 +4,19 @@ module Tootsie
4
4
 
5
5
  def initialize(options = {})
6
6
  @@instance = self
7
- @logger = options[:logger] || Logger.new($stderr)
8
7
  @configuration = Configuration.new
9
8
  end
10
9
 
11
10
  def configure!(config_path)
12
11
  @configuration.load_from_file(config_path)
12
+ case @configuration.log_path
13
+ when 'syslog'
14
+ @logger = SyslogLogger.new('tootsie')
15
+ when String
16
+ @logger = Logger.new(@configuration.log_path)
17
+ else
18
+ @logger = Logger.new($stderr)
19
+ end
13
20
  @queue = Tootsie::SqsQueue.new(@configuration.sqs_queue_name, sqs_service)
14
21
  @task_manager = TaskManager.new(@queue)
15
22
  end
@@ -4,13 +4,14 @@ module Tootsie
4
4
 
5
5
  def initialize
6
6
  @ffmpeg_thread_count = 1
7
+ @worker_count = 4
7
8
  @sqs_queue_name = 'tootsie'
8
9
  end
9
10
 
10
11
  def load_from_file(file_name)
11
12
  config = (YAML.load(File.read(file_name)) || {}).with_indifferent_access
12
13
  [:aws_access_key_id, :aws_secret_access_key, :ffmpeg_thread_count,
13
- :sqs_queue_name].each do |key|
14
+ :sqs_queue_name, :worker_count, :pid_path, :log_path].each do |key|
14
15
  if config.include?(key)
15
16
  value = config[key]
16
17
  value = $1.to_i if value =~ /\A\s*(\d+)\s*\z/
@@ -23,6 +24,9 @@ module Tootsie
23
24
  attr_accessor :aws_secret_access_key
24
25
  attr_accessor :ffmpeg_thread_count
25
26
  attr_accessor :sqs_queue_name
27
+ attr_accessor :pid_path
28
+ attr_accessor :log_path
29
+ attr_accessor :worker_count
26
30
 
27
31
  end
28
32
 
@@ -0,0 +1,118 @@
1
+ module Tootsie
2
+
3
+ # Runs the daemon from the command line.
4
+ class Runner
5
+
6
+ def initialize
7
+ @run_as_daemon = false
8
+ @config_path = '/etc/tootsie/tootsie.conf'
9
+ @app = Application.new
10
+ end
11
+
12
+ def run!(arguments = [])
13
+ OptionParser.new do |opts|
14
+ opts.banner = "Usage: #{File.basename($0)} [OPTIONS]"
15
+ opts.separator ""
16
+ opts.on("-d", "--daemon", 'Run as daemon') do
17
+ @run_as_daemon = true
18
+ end
19
+ opts.on("-p PATH", "--pid", "Store pid in PATH (defaults to #{@pid_path})") do |value|
20
+ @pid_path = File.expand_path(value)
21
+ end
22
+ opts.on("-c FILE", "--config FILE", "Read configuration from FILE (defaults to #{@config_path})") do |value|
23
+ @config_path = File.expand_path(value)
24
+ end
25
+ opts.on("-h", "--help", "Show this help.") do
26
+ puts opts
27
+ exit
28
+ end
29
+ opts.parse!(arguments)
30
+ end
31
+
32
+ @app.configure!(@config_path)
33
+
34
+ if @run_as_daemon
35
+ daemonize!
36
+ else
37
+ execute!
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def execute!
44
+ with_pid do
45
+ @spawner = Spawner.new(
46
+ :num_children => @app.configuration.worker_count,
47
+ :logger => logger)
48
+ @spawner.on_spawn do
49
+ $0 = "tootsie: worker"
50
+ Signal.trap('TERM') do
51
+ exit(2)
52
+ end
53
+ with_lifecycle_logging("Worker [#{Process.pid}]") do
54
+ @app.task_manager.run!
55
+ end
56
+ end
57
+ with_lifecycle_logging('Main process') do
58
+ @spawner.run
59
+ end
60
+ @spawner.terminate
61
+ end
62
+ end
63
+
64
+ def daemonize!(&block)
65
+ return Process.fork {
66
+ logger = @app.logger
67
+
68
+ Process.setsid
69
+ 0.upto(255) do |n|
70
+ File.for_fd(n, "r").close rescue nil
71
+ end
72
+
73
+ File.umask(27)
74
+ Dir.chdir('/')
75
+ $stdin.reopen("/dev/null", 'r')
76
+ $stdout.reopen("/dev/null", 'w')
77
+ $stderr.reopen("/dev/null", 'w')
78
+
79
+ Signal.trap("HUP") do
80
+ logger.debug("Ignoring SIGHUP")
81
+ end
82
+
83
+ execute!
84
+ }
85
+ end
86
+
87
+ def with_pid(&block)
88
+ path = @pid_path
89
+ path ||= @app.configuration.pid_path
90
+ path ||= '/var/run/tootsie.pid'
91
+
92
+ File.open(path, 'w') do |file|
93
+ file << Process.pid
94
+ end
95
+ begin
96
+ yield
97
+ ensure
98
+ File.delete(path) rescue nil
99
+ end
100
+ end
101
+
102
+ def with_lifecycle_logging(prefix, &block)
103
+ logger.info("#{prefix} starting")
104
+ yield
105
+ rescue SystemExit, Interrupt, SignalException
106
+ logger.info("#{prefix} signaled")
107
+ rescue Exception => e
108
+ logger.error("#{prefix} failed with exception #{e.class}: #{e}")
109
+ exit(1)
110
+ end
111
+
112
+ def logger
113
+ @app.logger
114
+ end
115
+
116
+ end
117
+
118
+ end
@@ -1,3 +1,3 @@
1
1
  module Tootsie
2
- VERSION = '0.9.3'
2
+ VERSION = '0.9.4'
3
3
  end
metadata CHANGED
@@ -1,266 +1,189 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: tootsie
3
- version: !ruby/object:Gem::Version
4
- hash: 61
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.4
5
5
  prerelease:
6
- segments:
7
- - 0
8
- - 9
9
- - 3
10
- version: 0.9.3
11
6
  platform: ruby
12
- authors:
7
+ authors:
13
8
  - Alexander Staubo
14
9
  autorequire:
15
10
  bindir: bin
16
11
  cert_chain: []
17
-
18
- date: 2012-01-20 00:00:00 Z
19
- dependencies:
20
- - !ruby/object:Gem::Dependency
12
+ date: 2012-01-20 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
21
15
  name: json
22
- prerelease: false
23
- requirement: &id001 !ruby/object:Gem::Requirement
16
+ requirement: &70337010813360 !ruby/object:Gem::Requirement
24
17
  none: false
25
- requirements:
18
+ requirements:
26
19
  - - ~>
27
- - !ruby/object:Gem::Version
28
- hash: 11
29
- segments:
30
- - 1
31
- - 4
32
- - 6
20
+ - !ruby/object:Gem::Version
33
21
  version: 1.4.6
34
22
  type: :runtime
35
- version_requirements: *id001
36
- - !ruby/object:Gem::Dependency
37
- name: sinatra
38
23
  prerelease: false
39
- requirement: &id002 !ruby/object:Gem::Requirement
24
+ version_requirements: *70337010813360
25
+ - !ruby/object:Gem::Dependency
26
+ name: sinatra
27
+ requirement: &70337010812840 !ruby/object:Gem::Requirement
40
28
  none: false
41
- requirements:
29
+ requirements:
42
30
  - - ~>
43
- - !ruby/object:Gem::Version
44
- hash: 15
45
- segments:
46
- - 1
47
- - 0
48
- version: "1.0"
31
+ - !ruby/object:Gem::Version
32
+ version: '1.0'
49
33
  type: :runtime
50
- version_requirements: *id002
51
- - !ruby/object:Gem::Dependency
52
- name: activesupport
53
34
  prerelease: false
54
- requirement: &id003 !ruby/object:Gem::Requirement
35
+ version_requirements: *70337010812840
36
+ - !ruby/object:Gem::Dependency
37
+ name: activesupport
38
+ requirement: &70337010812360 !ruby/object:Gem::Requirement
55
39
  none: false
56
- requirements:
40
+ requirements:
57
41
  - - ~>
58
- - !ruby/object:Gem::Version
59
- hash: 7
60
- segments:
61
- - 3
62
- - 0
63
- - 0
42
+ - !ruby/object:Gem::Version
64
43
  version: 3.0.0
65
44
  type: :runtime
66
- version_requirements: *id003
67
- - !ruby/object:Gem::Dependency
68
- name: httpclient
69
45
  prerelease: false
70
- requirement: &id004 !ruby/object:Gem::Requirement
46
+ version_requirements: *70337010812360
47
+ - !ruby/object:Gem::Dependency
48
+ name: httpclient
49
+ requirement: &70337010811880 !ruby/object:Gem::Requirement
71
50
  none: false
72
- requirements:
51
+ requirements:
73
52
  - - ~>
74
- - !ruby/object:Gem::Version
75
- hash: 5
76
- segments:
77
- - 2
78
- - 2
79
- - 1
53
+ - !ruby/object:Gem::Version
80
54
  version: 2.2.1
81
55
  type: :runtime
82
- version_requirements: *id004
83
- - !ruby/object:Gem::Dependency
84
- name: builder
85
56
  prerelease: false
86
- requirement: &id005 !ruby/object:Gem::Requirement
57
+ version_requirements: *70337010811880
58
+ - !ruby/object:Gem::Dependency
59
+ name: builder
60
+ requirement: &70337010811400 !ruby/object:Gem::Requirement
87
61
  none: false
88
- requirements:
62
+ requirements:
89
63
  - - ~>
90
- - !ruby/object:Gem::Version
91
- hash: 15
92
- segments:
93
- - 2
94
- - 1
95
- - 2
64
+ - !ruby/object:Gem::Version
96
65
  version: 2.1.2
97
66
  type: :runtime
98
- version_requirements: *id005
99
- - !ruby/object:Gem::Dependency
100
- name: mime-types
101
67
  prerelease: false
102
- requirement: &id006 !ruby/object:Gem::Requirement
68
+ version_requirements: *70337010811400
69
+ - !ruby/object:Gem::Dependency
70
+ name: mime-types
71
+ requirement: &70337010810920 !ruby/object:Gem::Requirement
103
72
  none: false
104
- requirements:
73
+ requirements:
105
74
  - - ~>
106
- - !ruby/object:Gem::Version
107
- hash: 47
108
- segments:
109
- - 1
110
- - 16
111
- version: "1.16"
75
+ - !ruby/object:Gem::Version
76
+ version: '1.16'
112
77
  type: :runtime
113
- version_requirements: *id006
114
- - !ruby/object:Gem::Dependency
115
- name: xml-simple
116
78
  prerelease: false
117
- requirement: &id007 !ruby/object:Gem::Requirement
79
+ version_requirements: *70337010810920
80
+ - !ruby/object:Gem::Dependency
81
+ name: xml-simple
82
+ requirement: &70337010810440 !ruby/object:Gem::Requirement
118
83
  none: false
119
- requirements:
84
+ requirements:
120
85
  - - ~>
121
- - !ruby/object:Gem::Version
122
- hash: 15
123
- segments:
124
- - 1
125
- - 0
126
- - 12
86
+ - !ruby/object:Gem::Version
127
87
  version: 1.0.12
128
88
  type: :runtime
129
- version_requirements: *id007
130
- - !ruby/object:Gem::Dependency
131
- name: thin
132
89
  prerelease: false
133
- requirement: &id008 !ruby/object:Gem::Requirement
90
+ version_requirements: *70337010810440
91
+ - !ruby/object:Gem::Dependency
92
+ name: thin
93
+ requirement: &70337010809960 !ruby/object:Gem::Requirement
134
94
  none: false
135
- requirements:
95
+ requirements:
136
96
  - - ~>
137
- - !ruby/object:Gem::Version
138
- hash: 17
139
- segments:
140
- - 1
141
- - 2
142
- - 7
97
+ - !ruby/object:Gem::Version
143
98
  version: 1.2.7
144
99
  type: :runtime
145
- version_requirements: *id008
146
- - !ruby/object:Gem::Dependency
147
- name: s3
148
100
  prerelease: false
149
- requirement: &id009 !ruby/object:Gem::Requirement
101
+ version_requirements: *70337010809960
102
+ - !ruby/object:Gem::Dependency
103
+ name: s3
104
+ requirement: &70337010809480 !ruby/object:Gem::Requirement
150
105
  none: false
151
- requirements:
106
+ requirements:
152
107
  - - ~>
153
- - !ruby/object:Gem::Version
154
- hash: 29
155
- segments:
156
- - 0
157
- - 3
158
- - 7
108
+ - !ruby/object:Gem::Version
159
109
  version: 0.3.7
160
110
  type: :runtime
161
- version_requirements: *id009
162
- - !ruby/object:Gem::Dependency
163
- name: sqs
164
111
  prerelease: false
165
- requirement: &id010 !ruby/object:Gem::Requirement
112
+ version_requirements: *70337010809480
113
+ - !ruby/object:Gem::Dependency
114
+ name: sqs
115
+ requirement: &70337010809000 !ruby/object:Gem::Requirement
166
116
  none: false
167
- requirements:
117
+ requirements:
168
118
  - - ~>
169
- - !ruby/object:Gem::Version
170
- hash: 31
171
- segments:
172
- - 0
173
- - 1
174
- - 2
119
+ - !ruby/object:Gem::Version
175
120
  version: 0.1.2
176
121
  type: :runtime
177
- version_requirements: *id010
178
- - !ruby/object:Gem::Dependency
179
- name: unicorn
180
122
  prerelease: false
181
- requirement: &id011 !ruby/object:Gem::Requirement
123
+ version_requirements: *70337010809000
124
+ - !ruby/object:Gem::Dependency
125
+ name: unicorn
126
+ requirement: &70337010808520 !ruby/object:Gem::Requirement
182
127
  none: false
183
- requirements:
128
+ requirements:
184
129
  - - ~>
185
- - !ruby/object:Gem::Version
186
- hash: 57
187
- segments:
188
- - 4
189
- - 1
190
- - 1
130
+ - !ruby/object:Gem::Version
191
131
  version: 4.1.1
192
132
  type: :runtime
193
- version_requirements: *id011
194
- - !ruby/object:Gem::Dependency
195
- name: i18n
196
133
  prerelease: false
197
- requirement: &id012 !ruby/object:Gem::Requirement
134
+ version_requirements: *70337010808520
135
+ - !ruby/object:Gem::Dependency
136
+ name: i18n
137
+ requirement: &70337010808040 !ruby/object:Gem::Requirement
198
138
  none: false
199
- requirements:
200
- - - ">="
201
- - !ruby/object:Gem::Version
202
- hash: 11
203
- segments:
204
- - 0
205
- - 4
206
- - 2
139
+ requirements:
140
+ - - ! '>='
141
+ - !ruby/object:Gem::Version
207
142
  version: 0.4.2
208
143
  type: :runtime
209
- version_requirements: *id012
210
- - !ruby/object:Gem::Dependency
211
- name: scashin133-syslog_logger
212
144
  prerelease: false
213
- requirement: &id013 !ruby/object:Gem::Requirement
145
+ version_requirements: *70337010808040
146
+ - !ruby/object:Gem::Dependency
147
+ name: scashin133-syslog_logger
148
+ requirement: &70337010807560 !ruby/object:Gem::Requirement
214
149
  none: false
215
- requirements:
150
+ requirements:
216
151
  - - ~>
217
- - !ruby/object:Gem::Version
218
- hash: 13
219
- segments:
220
- - 1
221
- - 7
222
- - 3
152
+ - !ruby/object:Gem::Version
223
153
  version: 1.7.3
224
154
  type: :runtime
225
- version_requirements: *id013
226
- - !ruby/object:Gem::Dependency
227
- name: rspec
228
155
  prerelease: false
229
- requirement: &id014 !ruby/object:Gem::Requirement
156
+ version_requirements: *70337010807560
157
+ - !ruby/object:Gem::Dependency
158
+ name: rspec
159
+ requirement: &70337010807180 !ruby/object:Gem::Requirement
230
160
  none: false
231
- requirements:
232
- - - ">="
233
- - !ruby/object:Gem::Version
234
- hash: 3
235
- segments:
236
- - 0
237
- version: "0"
161
+ requirements:
162
+ - - ! '>='
163
+ - !ruby/object:Gem::Version
164
+ version: '0'
238
165
  type: :development
239
- version_requirements: *id014
240
- - !ruby/object:Gem::Dependency
241
- name: rake
242
166
  prerelease: false
243
- requirement: &id015 !ruby/object:Gem::Requirement
167
+ version_requirements: *70337010807180
168
+ - !ruby/object:Gem::Dependency
169
+ name: rake
170
+ requirement: &70337010806720 !ruby/object:Gem::Requirement
244
171
  none: false
245
- requirements:
246
- - - ">="
247
- - !ruby/object:Gem::Version
248
- hash: 3
249
- segments:
250
- - 0
251
- version: "0"
172
+ requirements:
173
+ - - ! '>='
174
+ - !ruby/object:Gem::Version
175
+ version: '0'
252
176
  type: :development
253
- version_requirements: *id015
177
+ prerelease: false
178
+ version_requirements: *70337010806720
254
179
  description: Tootsie is a simple audio/video/image transcoding/modification application.
255
- email:
180
+ email:
256
181
  - alex@origo.no
257
- executables:
182
+ executables:
258
183
  - tootsie
259
184
  extensions: []
260
-
261
185
  extra_rdoc_files: []
262
-
263
- files:
186
+ files:
264
187
  - .gitignore
265
188
  - Gemfile
266
189
  - License
@@ -269,13 +192,11 @@ files:
269
192
  - Tootsie.gemspec
270
193
  - bin/tootsie
271
194
  - config.ru
272
- - config/development-sample.yml
273
195
  - lib/tootsie.rb
274
196
  - lib/tootsie/application.rb
275
197
  - lib/tootsie/client.rb
276
198
  - lib/tootsie/command_runner.rb
277
199
  - lib/tootsie/configuration.rb
278
- - lib/tootsie/daemon.rb
279
200
  - lib/tootsie/ffmpeg_adapter.rb
280
201
  - lib/tootsie/image_metadata_extractor.rb
281
202
  - lib/tootsie/input.rb
@@ -284,6 +205,7 @@ files:
284
205
  - lib/tootsie/processors/video_processor.rb
285
206
  - lib/tootsie/queues/file_system_queue.rb
286
207
  - lib/tootsie/queues/sqs_queue.rb
208
+ - lib/tootsie/runner.rb
287
209
  - lib/tootsie/s3_utilities.rb
288
210
  - lib/tootsie/spawner.rb
289
211
  - lib/tootsie/task_manager.rb
@@ -298,38 +220,29 @@ files:
298
220
  - spec/tootsie/s3_utilities_spec.rb
299
221
  homepage: http://github.com/alexstaubo/tootsie
300
222
  licenses: []
301
-
302
223
  post_install_message:
303
224
  rdoc_options: []
304
-
305
- require_paths:
225
+ require_paths:
306
226
  - lib
307
- required_ruby_version: !ruby/object:Gem::Requirement
227
+ required_ruby_version: !ruby/object:Gem::Requirement
308
228
  none: false
309
- requirements:
310
- - - ">="
311
- - !ruby/object:Gem::Version
312
- hash: 3
313
- segments:
314
- - 0
315
- version: "0"
316
- required_rubygems_version: !ruby/object:Gem::Requirement
229
+ requirements:
230
+ - - ! '>='
231
+ - !ruby/object:Gem::Version
232
+ version: '0'
233
+ required_rubygems_version: !ruby/object:Gem::Requirement
317
234
  none: false
318
- requirements:
319
- - - ">="
320
- - !ruby/object:Gem::Version
321
- hash: 3
322
- segments:
323
- - 0
324
- version: "0"
235
+ requirements:
236
+ - - ! '>='
237
+ - !ruby/object:Gem::Version
238
+ version: '0'
325
239
  requirements: []
326
-
327
240
  rubyforge_project: tootsie
328
241
  rubygems_version: 1.8.6
329
242
  signing_key:
330
243
  specification_version: 3
331
244
  summary: Tootsie is a simple audio/video/image transcoding/modification application.
332
- test_files:
245
+ test_files:
333
246
  - spec/spec_helper.rb
334
247
  - spec/test_files/BF 0622 1820.tif
335
248
  - spec/tootsie/command_runner_spec.rb
@@ -1,4 +0,0 @@
1
- ---
2
- aws_access_key_id: <your_key>
3
- aws_secret_access_key: <your_secret>
4
- sqs_queue_name: tootsie
@@ -1,282 +0,0 @@
1
- require "logger"
2
- require "fileutils"
3
- require 'optparse'
4
-
5
- module Tootsie
6
-
7
- class DaemonError < Exception; end
8
- class DaemonAlreadyRunning < DaemonError; end
9
- class DaemonNotRunning < DaemonError; end
10
- class DaemonTerminationFailed < DaemonError; end
11
- class DaemonNotConfigured < DaemonError; end
12
- class DaemonStartFailed < DaemonError; end
13
-
14
- # Daemon controller class that encapsulates a running daemon and a remote interface to it.
15
- class Daemon
16
-
17
- # Initializes the daemon controller.
18
- def initialize(options = {})
19
- @root = options[:root] || Dir.pwd
20
- @pid_file = options[:pid_file]
21
- @logger = options[:logger] || Logger.new('/dev/null')
22
- @on_spawn = nil
23
- end
24
-
25
- # Specifies a block to execute to run the actual daemon. Each call overrides the previous one.
26
- def on_spawn(&block)
27
- @on_spawn = block
28
- end
29
-
30
- # Specifies a block to execute to termiantion. Each call overrides the previous one.
31
- def on_terminate(&block)
32
- @on_terminate = block
33
- end
34
-
35
- # Control the daemon through command-line arguments.
36
- def control(args, title = nil)
37
- $stderr.sync = true
38
- title ||= File.basename($0)
39
- command = args.shift
40
- control_with_command(command, args, title)
41
- end
42
-
43
- # Control the daemon through a specific command.
44
- def control_with_command(command, args, title = nil)
45
- case command
46
- when "start"
47
- $stderr << "Starting #{title}: "
48
- handle_errors do
49
- start
50
- $stderr << "started\n"
51
- end
52
- when "stop"
53
- $stderr << "Stopping #{title}: "
54
- options = {}
55
- handle_errors do
56
- stop({:signal => "TERM"}.merge(options))
57
- $stderr << "stopped\n"
58
- end
59
- when "restart"
60
- $stderr << "Restarting #{title}: "
61
- handle_errors do
62
- restart
63
- $stderr << "restarted\n"
64
- end
65
- when "status"
66
- if running?
67
- $stderr << "#{title} is running\n"
68
- else
69
- $stderr << "#{title} is not running\n"
70
- end
71
- else
72
- if command
73
- $stderr << "#{File.basename($0)}: Invalid command #{command}\n"
74
- else
75
- puts "Usage: #{File.basename($0)} [start | stop | restart | status]"
76
- end
77
- end
78
- end
79
-
80
- # Starts daemon.
81
- def start
82
- raise DaemonNotConfigured, "Daemon not configured" unless @on_spawn
83
- FileUtils.mkdir_p(File.dirname(@pid_file)) rescue nil
84
- pid = self.pid
85
- if pid
86
- if pid_running?(pid)
87
- raise DaemonAlreadyRunning, "Process is already running with pid #{pid}"
88
- end
89
- end
90
- File.delete(@pid_file) rescue nil
91
- child_pid = Process.fork
92
- if child_pid
93
- sleep(1)
94
- unless running?
95
- pid = self.pid
96
- if pid == child_pid
97
- raise DaemonStartFailed, "Daemon started, but failed prematurely"
98
- else
99
- raise DaemonStartFailed, "Daemon failed to start for some unknown reason"
100
- end
101
- end
102
- return
103
- end
104
- class << logger
105
- def format_message(severity, timestamp, progname, msg)
106
- "[#{timestamp}] #{msg}\n"
107
- end
108
- end
109
- logger.info("Starting")
110
- begin
111
- Process.setsid
112
- 0.upto(255) do |n|
113
- File.for_fd(n, "r").close rescue nil
114
- end
115
- File.umask(27)
116
- Dir.chdir(@root)
117
- $stdin = File.open("/dev/null", File::RDWR)
118
- $stdout = File.open("/dev/null", File::RDWR)
119
- $stderr = File.open("/dev/null", File::RDWR)
120
- @pid = Process.pid
121
- File.open(@pid_file, "w") do |file|
122
- file << Process.pid
123
- end
124
- Signal.trap("HUP") do
125
- logger.debug("Ignoring SIGHUP")
126
- end
127
- Signal.trap("TERM") do
128
- if $$ == @pid
129
- logger.info("Terminating (#{$$})")
130
- @on_terminate.call if @on_terminate
131
- File.delete(@pid_file) rescue nil
132
- else
133
- # Was sent to a child
134
- end
135
- exit(0)
136
- end
137
- @on_spawn.call
138
- exit(0)
139
- rescue SystemExit
140
- # Do nothing
141
- rescue Exception => e
142
- message = "#{e.message}\n"
143
- message << e.backtrace.map { |line| "\tfrom #{line}\n" }.join
144
- logger.error(message)
145
- exit(1)
146
- ensure
147
- logger.close
148
- end
149
- end
150
-
151
- # Stops daemon.
152
- def stop(options = {})
153
- stopped = false
154
- found = false
155
- pid = self.pid
156
- if pid
157
- # Send TERM to process
158
- begin
159
- Process.kill(options[:signal] || "TERM", pid)
160
- rescue Errno::ESRCH
161
- stopped = true
162
- rescue Exception => e
163
- raise DaemonTerminationFailed, "Could not stop process #{pid}: #{e.message}"
164
- end
165
- unless stopped
166
- # Process was signaled, now wait for it to die
167
- found = true
168
- 30.times do
169
- begin
170
- if not pid_running?(pid)
171
- stopped = true
172
- break
173
- end
174
- sleep(1)
175
- rescue Exception => e
176
- raise DaemonTerminationFailed, "Could not stop process #{pid}: #{e.message}"
177
- end
178
- end
179
- if found and not stopped
180
- # Process still running after wait, force kill and wait
181
- begin
182
- Process.kill("KILL", pid)
183
- rescue Errno::ESRCH
184
- stopped = true
185
- end
186
- 30.times do
187
- begin
188
- if not pid_running?(pid)
189
- stopped = true
190
- break
191
- end
192
- sleep(1)
193
- rescue Exception => e
194
- raise DaemonTerminationFailed, "Could not stop process #{pid}: #{e.message}"
195
- end
196
- end
197
- if not stopped
198
- raise DaemonTerminationFailed, "Timeout attempting to stop process #{pid}"
199
- end
200
- end
201
- end
202
- end
203
- unless found
204
- raise DaemonNotRunning, "Process is not running"
205
- end
206
- end
207
-
208
- # Restarts daemon.
209
- def restart
210
- if running?
211
- begin
212
- stop
213
- rescue DaemonNotRunning
214
- # Ignore
215
- end
216
- end
217
- start
218
- end
219
-
220
- # Is the daemon running?
221
- def running?
222
- !pid.nil?
223
- end
224
-
225
- # Returns the daemon pid.
226
- def pid
227
- pid = nil
228
- maybe_pid = File.read(@pid_file) rescue nil
229
- if maybe_pid =~ /([0-9]+)/
230
- maybe_pid = $1.to_i
231
- begin
232
- Process.kill(0, maybe_pid)
233
- rescue Errno::ESRCH
234
- else
235
- pid = maybe_pid
236
- end
237
- end
238
- pid
239
- end
240
-
241
- # Signals the daemon.
242
- def signal(signal)
243
- pid = self.pid
244
- if pid
245
- Process.kill(signal, pid)
246
- else
247
- raise DaemonNotRunning, "Process is not running"
248
- end
249
- end
250
-
251
- attr_reader :root
252
- attr_reader :pid_file
253
- attr_reader :logger
254
-
255
- private
256
-
257
- def pid_running?(pid)
258
- begin
259
- Process.kill(0, pid)
260
- rescue Errno::ESRCH
261
- return false
262
- end
263
- return true
264
- end
265
-
266
- def handle_errors(&block)
267
- begin
268
- yield
269
- rescue DaemonError => e
270
- $stderr << "#{e.message}\n"
271
- if e.is_a?(DaemonAlreadyRunning) or e.is_a?(DaemonNotRunning)
272
- exit_code = 0
273
- else
274
- exit_code = 1
275
- end
276
- exit(exit_code)
277
- end
278
- end
279
-
280
- end
281
-
282
- end