videoreg 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/.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
|