videoreg 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +10 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +42 -0
- data/README.md +205 -0
- data/Rakefile +34 -0
- data/bin/videoreg +109 -0
- data/dist/videoreg-0.1.gem +0 -0
- data/install.sh +39 -0
- data/lib/videoreg.rb +232 -0
- data/lib/videoreg/base.rb +41 -0
- data/lib/videoreg/config.rb +55 -0
- data/lib/videoreg/lockfile.rb +100 -0
- data/lib/videoreg/registrar.rb +196 -0
- data/lib/videoreg/util.rb +80 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/videoreg/lockfile_spec.rb +26 -0
- data/spec/videoreg/registrar_spec.rb +33 -0
- data/spec/videoreg_spec.rb +16 -0
- data/videoreg-sample-config.rb +38 -0
- data/videoreg-sample.god +22 -0
- data/videoreg.gemspec +26 -0
- metadata +162 -0
data/lib/videoreg.rb
ADDED
@@ -0,0 +1,232 @@
|
|
1
|
+
Dir[File.dirname(File.expand_path(__FILE__))+"/videoreg/*.rb"].each { |f| require f }
|
2
|
+
require 'rubygems'
|
3
|
+
require 'logger'
|
4
|
+
require 'ostruct'
|
5
|
+
require 'amqp'
|
6
|
+
require 'json'
|
7
|
+
require_relative './videoreg/util'
|
8
|
+
|
9
|
+
####################################################
|
10
|
+
# Main videoreg module
|
11
|
+
module Videoreg
|
12
|
+
|
13
|
+
# configurable constants
|
14
|
+
VERSION = "0.1"
|
15
|
+
DAEMON_NAME = "videoreg"
|
16
|
+
MAX_THREAD_WAIT_LIMIT_SEC = 600 # time while daemon waits until capture thread exists
|
17
|
+
UDEV_RULES_FILE = '/etc/udev/rules.d/50-udev-videoreg.rules'
|
18
|
+
DEV_SYMLINK = "webcam" # name of the devices symlinks
|
19
|
+
|
20
|
+
# internal constants
|
21
|
+
ALLOWED_CONFIG_OPTIONS = %w[mq_host mq_queue pid_path log_path device]
|
22
|
+
MSG_HALT = 'HALT'
|
23
|
+
MSG_RECOVER = 'RECOVER'
|
24
|
+
MSG_PAUSE = 'PAUSE'
|
25
|
+
MSG_RESUME = 'RESUME'
|
26
|
+
MSG2ACTION = {MSG_HALT => :halt!, MSG_PAUSE => :pause!, MSG_RESUME => :resume!, MSG_RECOVER => :recover!}
|
27
|
+
|
28
|
+
@registered_regs = {}
|
29
|
+
@time_started = Time.now
|
30
|
+
@run_options = OpenStruct.new(
|
31
|
+
:device => :all,
|
32
|
+
:action => :run,
|
33
|
+
:log_path => 'videoreg.log',
|
34
|
+
:pid_path => '/tmp/videoreg.pid',
|
35
|
+
:mq_host => '127.0.0.1',
|
36
|
+
:mq_queue => 'ifree.videoreg.server'
|
37
|
+
)
|
38
|
+
|
39
|
+
class << self
|
40
|
+
# Version info
|
41
|
+
def version_info
|
42
|
+
"#{DAEMON_NAME} v.#{VERSION}"
|
43
|
+
end
|
44
|
+
|
45
|
+
# Disconnect from RabbitMQ
|
46
|
+
def mq_disconnect(connection)
|
47
|
+
logger.info "Disconnecting from RabbitMQ..."
|
48
|
+
connection.close { EM.stop { exit } }
|
49
|
+
end
|
50
|
+
|
51
|
+
# Listen the incoming messages from RabbitMQ
|
52
|
+
def mq_listen(&block)
|
53
|
+
Thread.new {
|
54
|
+
begin
|
55
|
+
logger.info "New messaging thread created for RabbitMQ #{opt.mq_host} / #{opt.mq_queue}"
|
56
|
+
AMQP.start(:host => opt.mq_host) do |connection|
|
57
|
+
q = AMQP::Channel.new(connection).queue(opt.mq_queue)
|
58
|
+
q.subscribe do |msg|
|
59
|
+
Videoreg::Base.logger.info "Received message from RabbitMQ #{msg}..."
|
60
|
+
block.call(connection, msg) if block_given?
|
61
|
+
end
|
62
|
+
Signal.add_trap("TERM") { q.delete; mq_disconnect(connection) }
|
63
|
+
Signal.add_trap(0) { q.delete; mq_disconnect(connection) }
|
64
|
+
end
|
65
|
+
rescue => e
|
66
|
+
logger.error "Error during establishing the connection to RabbitMQ: #{e.message}"
|
67
|
+
@dante_runner.stop if @dante_runner
|
68
|
+
end
|
69
|
+
}
|
70
|
+
end
|
71
|
+
|
72
|
+
# Send a message to the RabbitMQ
|
73
|
+
def mq_send(message, arg = nil)
|
74
|
+
AMQP.start(:host => opt.mq_host) do |connection|
|
75
|
+
channel = AMQP::Channel.new(connection)
|
76
|
+
logger.info "Publish message to RabbitMQ '#{message}' with arg '#{arg}' to '#{opt.mq_queue}'..."
|
77
|
+
channel.default_exchange.publish({:msg => message, :arg => arg}.to_json, :routing_key => opt.mq_queue)
|
78
|
+
EM.add_timer(0.5) { mq_disconnect(connection) }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def run_options
|
83
|
+
@run_options
|
84
|
+
end
|
85
|
+
|
86
|
+
def registrars
|
87
|
+
@registered_regs
|
88
|
+
end
|
89
|
+
|
90
|
+
def logger
|
91
|
+
Videoreg::Base.logger
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
####################################################
|
96
|
+
# Options
|
97
|
+
def opt(*args)
|
98
|
+
if !args.empty? && args[0].is_a?(Hash)
|
99
|
+
op = args[0].flatten
|
100
|
+
run_options.send("#{op[0]}=", op[1])
|
101
|
+
else
|
102
|
+
run_options
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Capture some other opts
|
107
|
+
ALLOWED_CONFIG_OPTIONS.each do |op|
|
108
|
+
self.send(:define_method, op) do |value|
|
109
|
+
opt.send("#{op}=".to_sym, value)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Shortcut to create new registrar's configuration
|
114
|
+
def reg(&block)
|
115
|
+
r = Registrar.new
|
116
|
+
Signal.add_trap(0) { r.safe_release! }
|
117
|
+
r.logger = opt.logger if opt.logger
|
118
|
+
r.config.instance_eval(&block)
|
119
|
+
registrars[r.config.device] = r
|
120
|
+
end
|
121
|
+
|
122
|
+
# Calculate current registrars list
|
123
|
+
def calc_reg_list(device = :all)
|
124
|
+
registrars.find_all { |dev, reg| device.to_sym == :all || device.to_s == dev }.map { |vp| vp[1] }
|
125
|
+
end
|
126
|
+
|
127
|
+
# Initiate new dante runner
|
128
|
+
def init_dante_runner
|
129
|
+
dante_opts = {:pid_path => '/tmp/videoreg.pid', :log_path => opt.log_path}
|
130
|
+
dante_opts.merge!(:kill => true) if opt.action == :kill
|
131
|
+
dante_opts.merge!(:pid_path => opt.pid_path) if opt.pid_path
|
132
|
+
Dante::Runner.new(DAEMON_NAME, dante_opts)
|
133
|
+
end
|
134
|
+
|
135
|
+
# Run daemon
|
136
|
+
def run_daemon(regs)
|
137
|
+
Signal.add_trap("TERM") { File.unlink(opt.pid_path) if File.exists?(opt.pid_path) }
|
138
|
+
@time_started = Time.now
|
139
|
+
# Run message thread
|
140
|
+
mq_listen do |connection, message|
|
141
|
+
begin
|
142
|
+
raise "Unexpected message struct received: #{message}!" unless (message = JSON.parse(message)).is_a?(Hash)
|
143
|
+
opt.device = message["arg"] if message["arg"]
|
144
|
+
if (action = MSG2ACTION[message["msg"]])
|
145
|
+
logger.info "#{message["msg"]} MESSAGE RECEIVED!"
|
146
|
+
calc_reg_list(opt.device).each { |reg| reg.send(action) }
|
147
|
+
else
|
148
|
+
logger.error "UNKNOWN MESSAGE RECEIVED!"
|
149
|
+
end
|
150
|
+
rescue => e
|
151
|
+
logger.error "Exception during incoming message processing: #{e.message}: \n#{e.backtrace.join("\n")}"
|
152
|
+
end
|
153
|
+
end
|
154
|
+
# Run main thread
|
155
|
+
regs.map { |reg|
|
156
|
+
logger.info "Starting continuous registration from device #{reg.device}..."
|
157
|
+
{:reg => reg, :thread => reg.continuous}
|
158
|
+
}.each { |reg_hash|
|
159
|
+
while true do # avoid deadlock exception
|
160
|
+
reg_hash[:thread].join(MAX_THREAD_WAIT_LIMIT_SEC)
|
161
|
+
break if reg_hash[:reg].terminated? # break if thread was terminated
|
162
|
+
end if reg_hash[:reg] && reg_hash[:thread]
|
163
|
+
}
|
164
|
+
@time_ended = Time.now
|
165
|
+
logger.info "Daemon finished execution. Uptime #{@time_ended - @time_started} sec"
|
166
|
+
end
|
167
|
+
|
168
|
+
# Shortcut to run action on registrar(s)
|
169
|
+
def run(device = :all, action = opt.action)
|
170
|
+
|
171
|
+
# Input
|
172
|
+
@registrars = calc_reg_list(device)
|
173
|
+
@dante_runner = init_dante_runner
|
174
|
+
opt.action, action = :run, :run if @dante_runner.daemon_stopped? && opt.action == :recover
|
175
|
+
|
176
|
+
# Main actions switch
|
177
|
+
puts "Running command '#{opt.action}' for device(s): '#{device}'..."
|
178
|
+
case action
|
179
|
+
when :kill then
|
180
|
+
@dante_runner.stop
|
181
|
+
when :pause then
|
182
|
+
mq_send(MSG_PAUSE, device) if @dante_runner.daemon_running?
|
183
|
+
when :resume then
|
184
|
+
mq_send(MSG_RESUME, device) if @dante_runner.daemon_running?
|
185
|
+
when :recover then
|
186
|
+
mq_send(MSG_RECOVER, device) if @dante_runner.daemon_running?
|
187
|
+
when :ensure then
|
188
|
+
uptime = (@dante_runner.daemon_running?) ? Time.now - File.stat(opt.pid_path).ctime : 0
|
189
|
+
[{:daemon_running? => @dante_runner.daemon_running?, :uptime => uptime}] + @registrars.map { |reg|
|
190
|
+
{
|
191
|
+
:device => reg.device,
|
192
|
+
:device_exists? => reg.device_exists?,
|
193
|
+
:process_alive? => reg.process_alive?,
|
194
|
+
:paused? => reg.paused?
|
195
|
+
}
|
196
|
+
}
|
197
|
+
when :halt then
|
198
|
+
mq_send(MSG_HALT, device) if @dante_runner.daemon_running?
|
199
|
+
when :run then
|
200
|
+
@dante_runner.execute(:daemonize => true) {
|
201
|
+
logger.info "Starting daemon with options: #{opt.marshal_dump}"
|
202
|
+
run_daemon(@registrars)
|
203
|
+
}
|
204
|
+
when :reset then
|
205
|
+
@registrars.each { |reg|
|
206
|
+
reg.force_release_lock!
|
207
|
+
}
|
208
|
+
logger.info "Forced to release pidfile #{opt.pid_path}"
|
209
|
+
File.unlink(opt.pid_path) if File.exists?(opt.pid_path)
|
210
|
+
else
|
211
|
+
raise "Unsupported action #{action} provided to runner!"
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
end
|
216
|
+
|
217
|
+
|
218
|
+
end
|
219
|
+
|
220
|
+
####################################################
|
221
|
+
# Extend ruby signal to support several listeners
|
222
|
+
def Signal.add_trap(sig, &block)
|
223
|
+
@added_signals = {} unless @added_signals
|
224
|
+
@added_signals[sig] = [] unless @added_signals[sig]
|
225
|
+
@added_signals[sig] << block
|
226
|
+
end
|
227
|
+
|
228
|
+
# catch interrupt signal && call all listeners
|
229
|
+
Signal.trap("TERM", proc { @added_signals && @added_signals[0].each { |p| p.call } })
|
230
|
+
|
231
|
+
|
232
|
+
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'stringio'
|
3
|
+
require_relative 'util'
|
4
|
+
module Videoreg
|
5
|
+
class Base
|
6
|
+
@@logger = ::Logger.new(STDOUT)
|
7
|
+
|
8
|
+
def logger
|
9
|
+
self.class.logger
|
10
|
+
end
|
11
|
+
|
12
|
+
def logger=(log)
|
13
|
+
self.class.logger=log
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.logger=(log)
|
17
|
+
@@logger = log
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.logger
|
21
|
+
@@logger
|
22
|
+
end
|
23
|
+
|
24
|
+
# Applies current context to the templated string
|
25
|
+
def tpl(str)
|
26
|
+
eval("\"#{str}\"")
|
27
|
+
end
|
28
|
+
|
29
|
+
# Check if process is alive
|
30
|
+
def proc_alive?(pid)
|
31
|
+
Videoreg::Util.proc_alive?(pid)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Cross-platform way of finding an executable in the $PATH.
|
35
|
+
#
|
36
|
+
# which('ruby') #=> /usr/bin/ruby
|
37
|
+
def which(cmd)
|
38
|
+
Videoreg::Util.which(cmd)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require_relative 'base'
|
2
|
+
module Videoreg
|
3
|
+
class Config < Base
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@device = '/dev/video1'
|
7
|
+
@resolution = '320x240'
|
8
|
+
@fps = 25
|
9
|
+
@duration = 10
|
10
|
+
@filename = '#{time}-#{devname}.DEFAULT.avi'
|
11
|
+
@command = 'ffmpeg -r #{fps} -s #{resolution} -f video4linux2 -r ntsc -i #{device} -vcodec mjpeg -t #{duration} -an -y #{outfile}'
|
12
|
+
@storage = '/tmp/#{devname}'
|
13
|
+
@lockfile = '/tmp/videoreg.#{devname}.DEFAULT.lck'
|
14
|
+
@store_max = 50
|
15
|
+
end
|
16
|
+
|
17
|
+
def time
|
18
|
+
"#{Time.new.strftime("%Y%m%d-%H%M%S%L")}"
|
19
|
+
end
|
20
|
+
|
21
|
+
def logger
|
22
|
+
@logger || self.class.logger
|
23
|
+
end
|
24
|
+
|
25
|
+
def store_max(*args)
|
26
|
+
(args.length > 0) ? (self.store_max=(args.shift)) : (@store_max.to_i)
|
27
|
+
end
|
28
|
+
|
29
|
+
def outfile
|
30
|
+
"#{storage}/#{filename}"
|
31
|
+
end
|
32
|
+
|
33
|
+
def base_cmd
|
34
|
+
command.split(" ").first
|
35
|
+
end
|
36
|
+
|
37
|
+
# set or get inner variable value
|
38
|
+
# depending on arguments count
|
39
|
+
def method_missing(*args)
|
40
|
+
if args.length == 2 || args[0] =~ /=$/
|
41
|
+
mname = args[0].to_s.gsub(/=/, '')
|
42
|
+
value = args.last
|
43
|
+
eval("@#{mname}='#{value}'")
|
44
|
+
elsif args.length == 1
|
45
|
+
tpl(eval("@#{args[0]}"))
|
46
|
+
else
|
47
|
+
super
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def devname
|
52
|
+
device.split('/').last
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module Videoreg
|
2
|
+
#
|
3
|
+
# Lockfile.rb -- Implement a simple lock file method
|
4
|
+
#
|
5
|
+
class Lockfile
|
6
|
+
attr_accessor :lockfile
|
7
|
+
|
8
|
+
KEY_LENGTH = 80
|
9
|
+
@lockcode = ""
|
10
|
+
|
11
|
+
def initialize(lckf)
|
12
|
+
@lockfile = lckf
|
13
|
+
end
|
14
|
+
|
15
|
+
def lock(initial_lockcode = nil)
|
16
|
+
@initial_lockcode = initial_lockcode
|
17
|
+
if File.exists?(@lockfile)
|
18
|
+
return false
|
19
|
+
else
|
20
|
+
create
|
21
|
+
## verify that we indeed did get the lock
|
22
|
+
verify
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def verify
|
27
|
+
if not File.exists?(@lockfile)
|
28
|
+
return false
|
29
|
+
end
|
30
|
+
if readlock == @lockcode.to_s
|
31
|
+
return true
|
32
|
+
else
|
33
|
+
return false
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def lockcode
|
38
|
+
readlock
|
39
|
+
end
|
40
|
+
|
41
|
+
def release
|
42
|
+
if self.verify
|
43
|
+
begin
|
44
|
+
File.delete(@lockfile)
|
45
|
+
@lockcode = ""
|
46
|
+
rescue Exception => e
|
47
|
+
return false
|
48
|
+
end
|
49
|
+
return true
|
50
|
+
else
|
51
|
+
return false
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def get_or_create_key
|
56
|
+
@initial_lockcode || create_key
|
57
|
+
end
|
58
|
+
|
59
|
+
def create_key
|
60
|
+
alpha = [('a'..'z'), ('A'..'Z')].map { |i| i.to_a }.flatten
|
61
|
+
return (0..KEY_LENGTH).map { alpha[rand(alpha.length)] }.join
|
62
|
+
end
|
63
|
+
|
64
|
+
def finalize(id)
|
65
|
+
#
|
66
|
+
# Ensure lock file is erased when object dies before being released
|
67
|
+
#
|
68
|
+
File.delete(@lockfile)
|
69
|
+
end
|
70
|
+
|
71
|
+
##-----------------##
|
72
|
+
private
|
73
|
+
##-----------------##
|
74
|
+
|
75
|
+
def create
|
76
|
+
@lockcode = get_or_create_key
|
77
|
+
begin
|
78
|
+
g = File.open(@lockfile, "w")
|
79
|
+
g.write @lockcode
|
80
|
+
g.close
|
81
|
+
rescue Exception => e
|
82
|
+
return false
|
83
|
+
end
|
84
|
+
return true
|
85
|
+
end
|
86
|
+
|
87
|
+
def readlock
|
88
|
+
code = ""
|
89
|
+
begin
|
90
|
+
g = File.open(@lockfile, "r")
|
91
|
+
code = g.read
|
92
|
+
g.close
|
93
|
+
rescue
|
94
|
+
return ""
|
95
|
+
end
|
96
|
+
return code
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,196 @@
|
|
1
|
+
require 'open4'
|
2
|
+
require 'pathname'
|
3
|
+
|
4
|
+
require_relative "base"
|
5
|
+
|
6
|
+
module Videoreg
|
7
|
+
class Registrar < Videoreg::Base
|
8
|
+
DELEGATE_TO_CONFIG = [:command, :outfile, :resolution, :fps, :duration, :device, :storage, :base_cmd, :store_max]
|
9
|
+
attr_reader :config
|
10
|
+
|
11
|
+
#################################
|
12
|
+
public # Public methods:
|
13
|
+
|
14
|
+
def initialize(&block)
|
15
|
+
@config = Videoreg::Config.new
|
16
|
+
@pid = nil
|
17
|
+
@halted_mutex = nil
|
18
|
+
@terminated = false
|
19
|
+
configure(&block) if block_given?
|
20
|
+
end
|
21
|
+
|
22
|
+
def configure
|
23
|
+
@thread = nil
|
24
|
+
yield @config
|
25
|
+
end
|
26
|
+
|
27
|
+
def method_missing(m)
|
28
|
+
DELEGATE_TO_CONFIG.include?(m.to_sym) ? config.send(m) : super
|
29
|
+
end
|
30
|
+
|
31
|
+
def continuous
|
32
|
+
logger.info "Starting the continuous capture..."
|
33
|
+
@terminated = false
|
34
|
+
@thread = Thread.new do
|
35
|
+
while true do
|
36
|
+
unless @halted_mutex.nil?
|
37
|
+
logger.info "Registrar (#{device}) HALTED. Waiting for the restore message..."
|
38
|
+
@halted_mutex.lock
|
39
|
+
end
|
40
|
+
unless device_exists?
|
41
|
+
logger.error "Capture failed! Device #{device} does not exist!"
|
42
|
+
terminate!
|
43
|
+
end
|
44
|
+
begin
|
45
|
+
logger.info "Cleaning old files from storage (#{storage})... (MAX: #{config.store_max})"
|
46
|
+
clean_old_files!
|
47
|
+
logger.info "Waiting for registrar (#{device}) to finish the part (#{outfile})..."
|
48
|
+
run # perform one registration
|
49
|
+
logger.info "Registrar (#{device}) has finished to capture the part (#{outfile})..."
|
50
|
+
rescue RuntimeError => e
|
51
|
+
logger.error(e.message)
|
52
|
+
logger.info "Registrar (#{device}) has failed to capture the part (#{outfile})..."
|
53
|
+
terminate!
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def run
|
60
|
+
logger.info "Spawning a new process to capture video from device '#{device}'..."
|
61
|
+
raise "Lockfile already exists '#{config.lockfile}'..." if File.exist?(config.lockfile)
|
62
|
+
logger.info "Running the command: '#{command}'..."
|
63
|
+
raise "#{base_cmd} not found on your system. Please install it or add it to your PATH" if which(base_cmd).nil?&& !File.exists?(base_cmd)
|
64
|
+
Open4::popen4(command) do |pid, stdin, stdout, stderr|
|
65
|
+
@pid = pid
|
66
|
+
raise "Cannot lock the lock-file '#{config.lockfile}'..." unless lock(pid)
|
67
|
+
output = stdout.read + stderr.read
|
68
|
+
raise "FATAL ERROR: Cannot capture video: \n #{output}" if error?(output)
|
69
|
+
end
|
70
|
+
ensure
|
71
|
+
release
|
72
|
+
end
|
73
|
+
|
74
|
+
def pid
|
75
|
+
@pid || ((rpid = lockfile.lockcode) ? rpid.to_i : nil)
|
76
|
+
end
|
77
|
+
|
78
|
+
def clean_old_files!
|
79
|
+
all_saved_files = Dir[Pathname.new(storage).join("*#{File.extname(config.filename)}").to_s].sort_by { |c|
|
80
|
+
File.stat(c).ctime
|
81
|
+
}.reverse
|
82
|
+
if all_saved_files.length > config.store_max.to_i
|
83
|
+
all_saved_files[config.store_max.to_i..-1].each do |saved_file|
|
84
|
+
logger.info "Removing saved file #{saved_file}..."
|
85
|
+
File.unlink(saved_file) if File.exists?(saved_file)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def halt!
|
91
|
+
logger.info "Registrar #{device} HALTED! Killing process..."
|
92
|
+
@halted_mutex = Mutex.new
|
93
|
+
@halted_mutex.lock
|
94
|
+
kill_process!
|
95
|
+
end
|
96
|
+
|
97
|
+
def pause!
|
98
|
+
logger.info "Registrar #{device} pausing process with pid #{pid}..."
|
99
|
+
Process.kill("STOP", pid) if process_alive?
|
100
|
+
end
|
101
|
+
|
102
|
+
def recover!
|
103
|
+
logger.info "Registrar #{device} UNHALTED! Recovering process..."
|
104
|
+
@halted_mutex.unlock if @halted_mutex && @halted_mutex.locked?
|
105
|
+
@halted_mutex = nil
|
106
|
+
end
|
107
|
+
|
108
|
+
def resume!
|
109
|
+
logger.info "Registrar #{device} resuming process with pid #{pid}..."
|
110
|
+
Process.kill("CONT", pid) if process_alive?
|
111
|
+
end
|
112
|
+
|
113
|
+
# Kill just the underlying process
|
114
|
+
def kill_process!
|
115
|
+
begin
|
116
|
+
logger.info("Killing the process for #{device} : #{pid}")
|
117
|
+
Process.kill("KILL", pid) if process_alive?
|
118
|
+
Process.getpgid
|
119
|
+
rescue => e
|
120
|
+
logger.warn("An attempt to kill already killed process (#{pid}): #{e.message}")
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Kill completely
|
125
|
+
def kill!
|
126
|
+
terminate!
|
127
|
+
kill_process! if process_alive?
|
128
|
+
ensure
|
129
|
+
safe_release!
|
130
|
+
end
|
131
|
+
|
132
|
+
# Terminate the main thread
|
133
|
+
def terminate!
|
134
|
+
@terminated = true
|
135
|
+
@thread.kill if @thread
|
136
|
+
ensure
|
137
|
+
safe_release!
|
138
|
+
end
|
139
|
+
|
140
|
+
def safe_release!
|
141
|
+
release if File.exists?(config.lockfile)
|
142
|
+
end
|
143
|
+
|
144
|
+
def force_release_lock!
|
145
|
+
logger.info("Forced to release lockfile #{config.lockfile}...")
|
146
|
+
File.unlink(config.lockfile) if File.exists?(config.lockfile)
|
147
|
+
end
|
148
|
+
|
149
|
+
def device_exists?
|
150
|
+
File.exists?(device)
|
151
|
+
end
|
152
|
+
|
153
|
+
def self_alive?
|
154
|
+
process_alive? && lockfile.readlock
|
155
|
+
end
|
156
|
+
|
157
|
+
def process_alive?
|
158
|
+
!pid.to_s.empty? && pid.to_i != 0 && proc_alive?(pid)
|
159
|
+
end
|
160
|
+
|
161
|
+
def terminated?
|
162
|
+
@terminated
|
163
|
+
end
|
164
|
+
|
165
|
+
def paused?
|
166
|
+
process_alive? && (`ps -p #{pid} -o stat=`.chomp == "T")
|
167
|
+
end
|
168
|
+
|
169
|
+
#################################
|
170
|
+
private # Private methods:
|
171
|
+
|
172
|
+
def error?(output)
|
173
|
+
if output =~ /No such file or directory/ || output =~ /I\/O error occurred/ ||
|
174
|
+
output =~ /Input\/output error/ || output =~ /ioctl\(VIDIOC_QBUF\)/
|
175
|
+
output.split("\n").last
|
176
|
+
else
|
177
|
+
nil
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def lockfile
|
182
|
+
@lockfile ||= Lockfile.new(config.lockfile)
|
183
|
+
end
|
184
|
+
|
185
|
+
def lock(pid)
|
186
|
+
logger.info "Locking registrar's #{config.device} (PID: #{pid}) lock file #{config.lockfile}..."
|
187
|
+
lockfile.lock(pid)
|
188
|
+
end
|
189
|
+
|
190
|
+
def release
|
191
|
+
logger.info "Releasing registrar's #{config.device} lock file #{config.lockfile}..."
|
192
|
+
lockfile.release
|
193
|
+
end
|
194
|
+
|
195
|
+
end
|
196
|
+
end
|