puremotion 0.0.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.
- data/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +20 -0
- data/README.rdoc +18 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/lib/puremotion/codecs.rb +59 -0
- data/lib/puremotion/events/event.rb +69 -0
- data/lib/puremotion/events/generator.rb +50 -0
- data/lib/puremotion/media/stream/audio.rb +0 -0
- data/lib/puremotion/media/stream/base.rb +7 -0
- data/lib/puremotion/media/stream/collection.rb +5 -0
- data/lib/puremotion/media/stream/video.rb +61 -0
- data/lib/puremotion/media/stream.rb +4 -0
- data/lib/puremotion/media.rb +490 -0
- data/lib/puremotion/recipes/ipod.yml +12 -0
- data/lib/puremotion/thread.rb +153 -0
- data/lib/puremotion/tools/ffmpeg.rb +141 -0
- data/lib/puremotion/transcode/recipe.rb +250 -0
- data/lib/puremotion/transcode/transcode.rb +153 -0
- data/lib/puremotion.rb +22 -0
- data/puremotion.gemspec +68 -0
- data/test/helper.rb +10 -0
- data/test/test_puremotion.rb +7 -0
- metadata +89 -0
@@ -0,0 +1,490 @@
|
|
1
|
+
module PureMotion
|
2
|
+
|
3
|
+
class Media
|
4
|
+
|
5
|
+
event :analysed
|
6
|
+
|
7
|
+
attr_accessor :file
|
8
|
+
|
9
|
+
@analysed = false
|
10
|
+
|
11
|
+
@unknown_format = false
|
12
|
+
@invalid_file = false
|
13
|
+
|
14
|
+
@streams = nil
|
15
|
+
|
16
|
+
def self.analyse(input, options = {})
|
17
|
+
return self.new(input)
|
18
|
+
end
|
19
|
+
|
20
|
+
def analysed?
|
21
|
+
@analysed
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(file)
|
25
|
+
@file = file
|
26
|
+
|
27
|
+
@output = ''
|
28
|
+
raise ArgumentError, "File does not exist" unless File.exists? @file
|
29
|
+
|
30
|
+
@ffmpeg = Tools::FFmpeg.new :options => "-i #{file}"
|
31
|
+
|
32
|
+
@ffmpeg.line + lambda do |ffmpeg, line|
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
@ffmpeg.complete + lambda do | ffmpeg, out |
|
37
|
+
@output = @ffmpeg.output.join("\n")
|
38
|
+
|
39
|
+
metadata = /(Input \#.*)\n.+\n\Z/m.match(@output)
|
40
|
+
|
41
|
+
@unknown_format = has(/Unknown format/i)
|
42
|
+
@invalid_file = has(/Duration: N\/A|bitrate: N\/A/im)
|
43
|
+
|
44
|
+
@analysed = true
|
45
|
+
analysed(true)
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
# Raw output of FFmpeg
|
51
|
+
#
|
52
|
+
# Returns:
|
53
|
+
#
|
54
|
+
# * Nil: If the video is still being analysed - unless the parameter :so_far is passed
|
55
|
+
# * String: Raw output
|
56
|
+
def output(opt)
|
57
|
+
|
58
|
+
return nil unless valid?
|
59
|
+
|
60
|
+
if status(:analysing) then return nil unless opt == :so_far end
|
61
|
+
|
62
|
+
return output.join('\n')
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
def transcode(params)
|
67
|
+
return Transcode::Transcode.new(self, params)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns:
|
71
|
+
# * nil if not yet analysed
|
72
|
+
# * false if the media is an unknown format
|
73
|
+
# * false if the file is not media or is corrupt
|
74
|
+
# * true otherwise
|
75
|
+
def valid?
|
76
|
+
return nil unless @analysed
|
77
|
+
return false if @unknown_format || @invalid_file
|
78
|
+
has /Input #\d*.\d*/
|
79
|
+
end
|
80
|
+
|
81
|
+
def invalid?
|
82
|
+
!valid?
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
def unknown_format?
|
87
|
+
@unknown_format
|
88
|
+
end
|
89
|
+
|
90
|
+
# If true then either:
|
91
|
+
# * The media is a valid format but corrupted and cannot be read
|
92
|
+
# or
|
93
|
+
# * The file isn't media!
|
94
|
+
def invalid_file?
|
95
|
+
@invalid_file
|
96
|
+
end
|
97
|
+
|
98
|
+
# Container bitrate:
|
99
|
+
#
|
100
|
+
# Returns:
|
101
|
+
# * Nil: If the bitrate could not be detected
|
102
|
+
# * String: "xxx kb/s" if _fmt_ is :raw
|
103
|
+
# * Integer: bitrate in kb/s otherwise
|
104
|
+
def bitrate(fmt = nil)
|
105
|
+
br = bitrate_match
|
106
|
+
return nil if br.nil?
|
107
|
+
|
108
|
+
mbr = {
|
109
|
+
:value => br[1].to_i,
|
110
|
+
:unit => br[2]
|
111
|
+
}
|
112
|
+
|
113
|
+
return mbr[:value] unless fmt == :raw
|
114
|
+
|
115
|
+
br
|
116
|
+
|
117
|
+
end
|
118
|
+
|
119
|
+
# Boolean: Whether media has a video stream or not
|
120
|
+
def video?
|
121
|
+
return nil if not @analysed
|
122
|
+
return false if not valid?
|
123
|
+
!video_match.nil?
|
124
|
+
end
|
125
|
+
|
126
|
+
# Boolean: Whether media has an audio stream or not
|
127
|
+
def audio?
|
128
|
+
return nil if not @analysed
|
129
|
+
has /Stream\s*(.*?)[,|:|\(|\[].*?\s*Video:\s*(.*?),\s*(.*?),\s*(\d*)x(\d*)/
|
130
|
+
end
|
131
|
+
|
132
|
+
# Returns a hash with all sorts of details about the first video stream
|
133
|
+
# Nil if no video stream
|
134
|
+
def video
|
135
|
+
return nil unless video?
|
136
|
+
{
|
137
|
+
:codec => detail(:video, :codec),
|
138
|
+
:colorspace => detail(:video, :colorspace),
|
139
|
+
:resolution => {
|
140
|
+
:width => detail(:video, :resolution, :width),
|
141
|
+
:height => detail(:video, :resolution, :height)
|
142
|
+
},
|
143
|
+
:fps => detail(:video, :fps),
|
144
|
+
:bitrate => detail(:video, :bitrate, :raw),
|
145
|
+
:duration => detail(:duration, :seconds)
|
146
|
+
}
|
147
|
+
end
|
148
|
+
|
149
|
+
def audio
|
150
|
+
return nil unless audio?
|
151
|
+
{
|
152
|
+
:codec => detail(:audio, :codec),
|
153
|
+
:sample_rate => detail(:audio, :sample, :rate),
|
154
|
+
:sample_size => detail(:audio, :sample, :size),
|
155
|
+
:channels => detail(:audio, :channels),
|
156
|
+
:bitrate => detail(:audio, :bitrate)
|
157
|
+
}
|
158
|
+
end
|
159
|
+
|
160
|
+
# Big messy function that allows things like:
|
161
|
+
# * detail :video, :resolution, :height
|
162
|
+
# * detail :video, :fps
|
163
|
+
# * detail :video, :duration
|
164
|
+
def detail(*args)
|
165
|
+
|
166
|
+
return nil unless valid?
|
167
|
+
|
168
|
+
case args[0]
|
169
|
+
when :video
|
170
|
+
|
171
|
+
# Main Video handler
|
172
|
+
|
173
|
+
case args[1]
|
174
|
+
when :codec
|
175
|
+
|
176
|
+
# Find video codec
|
177
|
+
return video_match[2]
|
178
|
+
|
179
|
+
when :colorspace
|
180
|
+
|
181
|
+
# Find colorspace
|
182
|
+
return video_match[3]
|
183
|
+
|
184
|
+
when :resolution
|
185
|
+
|
186
|
+
# Handle resolution
|
187
|
+
|
188
|
+
case args[2]
|
189
|
+
when :width
|
190
|
+
|
191
|
+
# Frame width
|
192
|
+
return video_match[4]
|
193
|
+
|
194
|
+
when :height
|
195
|
+
|
196
|
+
# Frame height
|
197
|
+
return video_match[5]
|
198
|
+
|
199
|
+
end
|
200
|
+
|
201
|
+
# Default resolution output format
|
202
|
+
return video_match[4] + "x" + video_match[5]
|
203
|
+
|
204
|
+
when :fps
|
205
|
+
|
206
|
+
# Frame Rate
|
207
|
+
fps = video_frame_rate_match[1].to_f
|
208
|
+
|
209
|
+
# Handle undected frame rates
|
210
|
+
if fps == 0 then fps = nil end
|
211
|
+
|
212
|
+
return fps
|
213
|
+
|
214
|
+
when :bitrate
|
215
|
+
|
216
|
+
# Captue bitrate via regex
|
217
|
+
br = video_bitrate_match[1].to_i
|
218
|
+
|
219
|
+
# Decide if bitrate is in kb/s or b/s
|
220
|
+
case video_bitrate_match[2]
|
221
|
+
when 'b/s'
|
222
|
+
|
223
|
+
# If in b/s divide to get kb/s
|
224
|
+
return (br / 1024).to_i
|
225
|
+
|
226
|
+
end
|
227
|
+
|
228
|
+
# Otherwise send raw bitrate
|
229
|
+
return br
|
230
|
+
|
231
|
+
when :duration
|
232
|
+
|
233
|
+
# Redirect to overall file duration
|
234
|
+
return detail(:duration, args[1])
|
235
|
+
|
236
|
+
end
|
237
|
+
|
238
|
+
# End video section
|
239
|
+
|
240
|
+
return nil
|
241
|
+
|
242
|
+
when :audio
|
243
|
+
|
244
|
+
# Audio section
|
245
|
+
|
246
|
+
case args[1]
|
247
|
+
when :codec
|
248
|
+
|
249
|
+
# Find the codec
|
250
|
+
codec = audio_match[2]
|
251
|
+
|
252
|
+
if codec =~ /\dx[\d a-f]*/ then
|
253
|
+
codec = :unidentifed
|
254
|
+
end
|
255
|
+
|
256
|
+
return codec
|
257
|
+
|
258
|
+
when :sample
|
259
|
+
|
260
|
+
case args[2]
|
261
|
+
when :rate
|
262
|
+
return audio_match[3].to_i
|
263
|
+
end
|
264
|
+
|
265
|
+
return nil
|
266
|
+
|
267
|
+
when :channels
|
268
|
+
|
269
|
+
case audio_match[5]
|
270
|
+
when 'stereo'
|
271
|
+
return 2
|
272
|
+
when 'mono'
|
273
|
+
return 1
|
274
|
+
end
|
275
|
+
|
276
|
+
return nil
|
277
|
+
when :bitrate
|
278
|
+
return audio_bitrate_match[1].to_i
|
279
|
+
end
|
280
|
+
|
281
|
+
return nil
|
282
|
+
|
283
|
+
when :duration
|
284
|
+
|
285
|
+
d = dur
|
286
|
+
secs = (d[:h] * 60 * 60) + (d[:m] * 60) + (d[:s]) * 1.00
|
287
|
+
|
288
|
+
case args[1]
|
289
|
+
when :seconds
|
290
|
+
return secs
|
291
|
+
when :frames
|
292
|
+
return (secs * video_frame_rate_match[1].to_f).to_i
|
293
|
+
end
|
294
|
+
|
295
|
+
return d[:raw]
|
296
|
+
|
297
|
+
end
|
298
|
+
|
299
|
+
nil
|
300
|
+
end
|
301
|
+
|
302
|
+
# * Float: Frame rate of video stream
|
303
|
+
# * Nil: If no video stream, undectable or invalid media
|
304
|
+
def fps
|
305
|
+
return nil unless valid?
|
306
|
+
return nil unless video?
|
307
|
+
begin
|
308
|
+
return video_frame_rate_match[1].to_f
|
309
|
+
rescue
|
310
|
+
return nil
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
private
|
315
|
+
|
316
|
+
def bitrate_match
|
317
|
+
/bitrate: ([0-9\.]+)\s*(.*)\s+/.match(@raw_metadata)
|
318
|
+
end
|
319
|
+
|
320
|
+
def audio_match
|
321
|
+
return nil unless valid?
|
322
|
+
|
323
|
+
match = /Stream\s*(.*?)[,|:|\(|\[].*?\s*Audio:\s*(.*?),\s*([0-9\.]*) (\w*),\s*([a-zA-Z:]*)/.match(@output)
|
324
|
+
|
325
|
+
match
|
326
|
+
end
|
327
|
+
|
328
|
+
def audio_bitrate_match
|
329
|
+
return nil if not valid?
|
330
|
+
|
331
|
+
match = /Stream\s*.*?[,|:|\(|\[].*?\s*Audio:\s*.*?,\s*[0-9\.]* \w*,\s*[a-zA-Z:]*,[^,]*,\s*(\d*)\s*([a-zA-Z]*\/s)/.match(@output)
|
332
|
+
|
333
|
+
match
|
334
|
+
end
|
335
|
+
|
336
|
+
def video_match
|
337
|
+
return nil unless valid?
|
338
|
+
|
339
|
+
match = /Stream\s*(.*?)[,|:|\(|\[].*?\s*Video:\s*(.*?),\s*(.*?),\s*(\d*)x(\d*)/.match(@output)
|
340
|
+
|
341
|
+
/#
|
342
|
+
|
343
|
+
1 => stream id
|
344
|
+
2 => codec
|
345
|
+
3 => colorspace
|
346
|
+
4 => width
|
347
|
+
5 => height
|
348
|
+
|
349
|
+
|
350
|
+
/
|
351
|
+
|
352
|
+
match
|
353
|
+
end
|
354
|
+
|
355
|
+
def video_bitrate_match
|
356
|
+
match = /Stream\s*.*?[,|:|\(|\[].*?\s*Video:\s*.*?,\s*.*?,\s*\d*x\d*[^,]*,\s*([0-9\.]+)\s*(.*\/.),/.match(@output)
|
357
|
+
match
|
358
|
+
end
|
359
|
+
|
360
|
+
def video_frame_rate_match
|
361
|
+
match = /Stream\s*.*?[,|:|\(|\[].*?\s*Video:\s\S*,\s\S*,\s\d+x\d+[^,]+,\s\S*\s\S*,\s([^,\s]*)/.match(@output)
|
362
|
+
|
363
|
+
match
|
364
|
+
end
|
365
|
+
|
366
|
+
def raw_duration
|
367
|
+
return nil unless valid?
|
368
|
+
|
369
|
+
/Duration:\s*([0-9\:\.]+),/.match(@output)[1]
|
370
|
+
end
|
371
|
+
|
372
|
+
def dur
|
373
|
+
return nil unless valid?
|
374
|
+
|
375
|
+
bits = /(\d*):(\d{2}):(\d{2}.\d*)/.match(raw_duration)
|
376
|
+
{
|
377
|
+
:raw => bits[0],
|
378
|
+
:h => bits[1].to_i,
|
379
|
+
:m => bits[2].to_i,
|
380
|
+
:s => bits[3].to_f
|
381
|
+
}
|
382
|
+
end
|
383
|
+
|
384
|
+
def has(regexp)
|
385
|
+
if @output =~ regexp then
|
386
|
+
true
|
387
|
+
else
|
388
|
+
false
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
def find(regexp)
|
393
|
+
m = regexp.match(@output)
|
394
|
+
return m[1] if m
|
395
|
+
nil
|
396
|
+
end
|
397
|
+
|
398
|
+
end
|
399
|
+
|
400
|
+
class Stream
|
401
|
+
|
402
|
+
def initialize(raw)
|
403
|
+
@raw = raw
|
404
|
+
end
|
405
|
+
|
406
|
+
def kind
|
407
|
+
m = /:\s*(\S*):/.match(@raw)
|
408
|
+
m[1]
|
409
|
+
end
|
410
|
+
|
411
|
+
end
|
412
|
+
|
413
|
+
class Input
|
414
|
+
|
415
|
+
attr_accessor :streams
|
416
|
+
|
417
|
+
def initialize(raw)
|
418
|
+
@raw = raw
|
419
|
+
@streams = []
|
420
|
+
detect_streams
|
421
|
+
end
|
422
|
+
|
423
|
+
def container
|
424
|
+
/Input \#\d+\,\s*(\S+),\s*from/.match(@raw)[1]
|
425
|
+
end
|
426
|
+
|
427
|
+
def file
|
428
|
+
/from '(\S+)'/.match(@raw)[1]
|
429
|
+
end
|
430
|
+
|
431
|
+
protected
|
432
|
+
|
433
|
+
def detect_streams
|
434
|
+
@raw.each_line do |line|
|
435
|
+
@streams << Stream.new(line) if line =~ /\s*Stream/
|
436
|
+
end
|
437
|
+
end
|
438
|
+
|
439
|
+
end
|
440
|
+
|
441
|
+
class Analysis
|
442
|
+
|
443
|
+
attr_accessor :info, :inputs
|
444
|
+
|
445
|
+
def initialize(options = {})
|
446
|
+
analyse
|
447
|
+
@inputs[0] unless @inputs.empty?
|
448
|
+
end
|
449
|
+
|
450
|
+
def analyse
|
451
|
+
@inputs = []
|
452
|
+
@fio = IO.popen('ffmpeg -i C:/ffmpeg/test.wmv 2>&1')
|
453
|
+
|
454
|
+
prev_indent = -1
|
455
|
+
indent = 0
|
456
|
+
lines = []
|
457
|
+
|
458
|
+
begin
|
459
|
+
f = @fio.gets
|
460
|
+
|
461
|
+
indent = f.length - f.lstrip.length
|
462
|
+
|
463
|
+
if indent < prev_indent then
|
464
|
+
add(lines.join)
|
465
|
+
lines = []
|
466
|
+
end
|
467
|
+
|
468
|
+
prev_indent = indent
|
469
|
+
|
470
|
+
#puts "Indent: " + indent.to_s + " - " + f
|
471
|
+
|
472
|
+
if f =~ /Input #\d*/ then
|
473
|
+
|
474
|
+
end
|
475
|
+
|
476
|
+
lines << f
|
477
|
+
|
478
|
+
end while not @fio.eof?
|
479
|
+
|
480
|
+
end
|
481
|
+
|
482
|
+
def add(block)
|
483
|
+
if m = /^Input #(\d+),/.match(block) then
|
484
|
+
@inputs[m[0].to_i] = Input.new block
|
485
|
+
end
|
486
|
+
end
|
487
|
+
|
488
|
+
end
|
489
|
+
|
490
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
# Author:: Jared Kuolt (mailto:me@superjared.com)
|
2
|
+
# Copyright:: Copyright (c) 2009 Jared Kuolt
|
3
|
+
# License:: MIT License
|
4
|
+
#
|
5
|
+
# See README.rdoc[link:files/README_rdoc.html] for usage
|
6
|
+
#
|
7
|
+
require 'logger'
|
8
|
+
require 'timeout'
|
9
|
+
|
10
|
+
module PureMotion
|
11
|
+
class Thread
|
12
|
+
# The Thread object, brah
|
13
|
+
attr_reader :thread
|
14
|
+
# If the Thread takes a poopie...
|
15
|
+
attr_reader :exception
|
16
|
+
# An identifier
|
17
|
+
attr_accessor :label
|
18
|
+
|
19
|
+
# Create a new RobustThread (see README)
|
20
|
+
def initialize(opts={}, &block)
|
21
|
+
self.class.send :init_exit_handler
|
22
|
+
args = (opts[:args] or [])
|
23
|
+
self.class.send :do_before_init
|
24
|
+
@thread = ::Thread.new(*args) do |*targs|
|
25
|
+
begin
|
26
|
+
self.class.send :do_before_yield
|
27
|
+
block.call(*targs)
|
28
|
+
self.class.send :do_after_yield
|
29
|
+
rescue Exception => e
|
30
|
+
@exception = e
|
31
|
+
self.class.send :handle_exception, e
|
32
|
+
end
|
33
|
+
self.class.log "#{self.label.inspect} exited cleanly"
|
34
|
+
end
|
35
|
+
self.label = opts[:label] || @thread.inspect
|
36
|
+
self.class.group << self
|
37
|
+
end
|
38
|
+
|
39
|
+
## Class methods and attributes
|
40
|
+
class << self
|
41
|
+
attr_accessor :logger, :say_goodnight, :exit_handler_initialized, :callbacks
|
42
|
+
VALID_CALLBACKS = [:before_init, :before_yield, :after_yield, :after_join, :before_exit]
|
43
|
+
|
44
|
+
# Logger object (see README)
|
45
|
+
def logger
|
46
|
+
@logger ||= Logger.new(STDOUT)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Simple log interface
|
50
|
+
def log(msg, level=:info)
|
51
|
+
#self.logger.send level, "#{self}: " + msg
|
52
|
+
end
|
53
|
+
|
54
|
+
# The collection of RobustThread objects
|
55
|
+
def group
|
56
|
+
@group ||= []
|
57
|
+
end
|
58
|
+
|
59
|
+
# Loop an activity and exit it cleanly (see README)
|
60
|
+
def loop(opts={}, &block)
|
61
|
+
sleep_seconds = opts.delete(:seconds) || 2
|
62
|
+
self.new(opts) do |*args|
|
63
|
+
Kernel.loop do
|
64
|
+
break if self.say_goodnight
|
65
|
+
block.call(*args)
|
66
|
+
# We want to sleep for the right amount of time, but we also don't
|
67
|
+
# want to wait until the sleep is done if our exit handler has been
|
68
|
+
# called so we iterate over a loop, sleeping only 0.1 and checking
|
69
|
+
# each iteration whether we need to die, and the timeout is a noop
|
70
|
+
# indicating we need to continue.
|
71
|
+
begin
|
72
|
+
Timeout.timeout(sleep_seconds) do
|
73
|
+
Kernel.loop do
|
74
|
+
break if self.say_goodnight
|
75
|
+
sleep 0.1
|
76
|
+
end
|
77
|
+
end
|
78
|
+
rescue Timeout::Error
|
79
|
+
# OK
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Set exception handler
|
86
|
+
def exception_handler(&block)
|
87
|
+
unless block.arity == 1
|
88
|
+
raise ArgumentError, "Bad arity for exception handler. It may only accept a single argument"
|
89
|
+
end
|
90
|
+
@exception_handler = block
|
91
|
+
end
|
92
|
+
|
93
|
+
# Add a callback
|
94
|
+
public
|
95
|
+
def add_callback(sym, &block)
|
96
|
+
sym = sym.to_sym
|
97
|
+
raise ArgumentError, "Invalid callback #{sym.inspect}" unless VALID_CALLBACKS.include? sym
|
98
|
+
self.callbacks ||= {}
|
99
|
+
self.callbacks[sym] ||= []
|
100
|
+
self.callbacks[sym] << block
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
# Calls exception handler if set (see RobustThread.exception_handler)
|
105
|
+
def handle_exception(exc)
|
106
|
+
if @exception_handler.is_a? Proc
|
107
|
+
@exception_handler.call(exc)
|
108
|
+
else
|
109
|
+
log("Unhandled exception:\n#{exc.message} " \
|
110
|
+
"(#{exc.class}): \n\t#{exc.backtrace.join("\n\t")}", :error)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# Sets up the exit_handler unless exit_handler_initialized
|
115
|
+
def init_exit_handler
|
116
|
+
return if self.exit_handler_initialized
|
117
|
+
self.say_goodnight = false
|
118
|
+
at_exit do
|
119
|
+
self.say_goodnight = true
|
120
|
+
begin
|
121
|
+
self.group.each do |rt|
|
122
|
+
log "waiting on #{rt.label.inspect}" if rt.thread.alive?
|
123
|
+
rt.thread.join
|
124
|
+
rt.class.send :do_after_join
|
125
|
+
end
|
126
|
+
self.send :do_before_exit
|
127
|
+
log "exited cleanly"
|
128
|
+
rescue Interrupt
|
129
|
+
log "prematurely killed by interrupt!", :error
|
130
|
+
end
|
131
|
+
end
|
132
|
+
self.exit_handler_initialized = true
|
133
|
+
end
|
134
|
+
|
135
|
+
def perform_callback(sym)
|
136
|
+
raise ArgumentError, "Cannot perform invalid callback #{sym.inspect}" unless VALID_CALLBACKS.include? sym
|
137
|
+
return unless self.callbacks and self.callbacks[sym]
|
138
|
+
self.callbacks[sym].reverse.each do |callback|
|
139
|
+
callback.call
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Performs callback, if possible
|
144
|
+
def method_missing(sym, *args)
|
145
|
+
if sym.to_s =~ /^do_(.*)$/
|
146
|
+
perform_callback($1.to_sym)
|
147
|
+
else
|
148
|
+
raise NoMethodError, "RobustThread method_missing: #{sym.inspect}"
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|