This commit is contained in:
Denis Knauf 2010-02-26 02:14:41 +01:00
commit 54a825211e
6 changed files with 367 additions and 0 deletions

1
AUTHOR Normal file
View file

@ -0,0 +1 @@
Denis Knauf <denis dot knauf at gmail dot com>

0
README.md Normal file
View file

58
Rakefile Normal file
View file

@ -0,0 +1,58 @@
require 'rubygems'
require 'rake'
begin
require 'jeweler'
Jeweler::Tasks.new do |gem|
gem.name = "svdrpd"
gem.summary = %Q{Multiplexer for SVDRP}
gem.description = %Q{Allowes to more than one connection to communicate with VDR}
gem.email = "Denis.Knauf@gmail.com"
gem.homepage = "http://github.com/DenisKnauf/svdrpd"
gem.authors = ["Denis Knauf"]
gem.files = ["README.md", "VERSION", "bin/**/*", "lib/**/*.rb", "test/**/*.rb"]
gem.require_paths = ["bin"]
gem.add_dependency 'select'
end
Jeweler::GemcutterTasks.new
rescue LoadError
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
end
require 'rake/testtask'
Rake::TestTask.new(:test) do |test|
test.libs << 'lib' << 'test' << 'ext'
test.pattern = 'test/**/*_test.rb'
test.verbose = true
end
begin
require 'rcov/rcovtask'
Rcov::RcovTask.new do |test|
test.libs << 'test'
test.pattern = 'test/**/*_test.rb'
test.verbose = true
end
rescue LoadError
task :rcov do
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
end
end
task :test => :check_dependencies
task :default => :test
require 'rake/rdoctask'
Rake::RDocTask.new do |rdoc|
if File.exist?('VERSION')
version = File.read('VERSION')
else
version = ""
end
rdoc.rdoc_dir = 'rdoc'
rdoc.title = "sbdb #{version}"
rdoc.rdoc_files.include('README*')
rdoc.rdoc_files.include('lib/**/*.rb')
end

1
VERSION Normal file
View file

@ -0,0 +1 @@
0.1.0

263
bin/svdrpd Executable file
View file

@ -0,0 +1,263 @@
#!/usr/bin/ruby
require 'socket'
require 'syslog'
require 'select'
module Kernel
def debug line
STDOUT.puts "#{caller[0]}: #{line}"
end
end
class SVDRPC <Select::Socket
def initialize opts
opts.update( :delimiter => /\r?\n/)
@vdr = opts[ :vdr] || raise( ArgumentError, "need VDR")
super opts
@sock.puts "220 #{ENV["HOSTNAME"]} SVDRP svdrpd 0.0.1; #{Time.now}"
end
def event_line line
if /^\s*quit/i.match line
self.quit "quit"
else
@vdr.push self, line
end
end
def event_write *args
self.close if super( *args).empty? && @quit
end
def quit reason = "unknown reason"
@sock.close_read
@select.del @sock, :read
@quit = true
puts "221 #{ENV["HOSTNAME"]} closing connection (#{reason})"
rescue IOError
end
end
class SVDRPD <Select::Server
def initialize opts
@vdr = opts[ :vdr] || raise( ArgumentError, "need VDR")
super opts
end
def event_new_client sock
{ :vdr => @vdr, :clientclass => SVDRPC }
end
def quit reason = "unknown reason"
self.close
@clients.each do |i|
i.quit reason
end
end
end
class VDR
Request = Struct.new :client, :str
attr_reader :sock, :serv, :port, :select, :firstline
def initialize host = 'localhost', port = 2001, select = Select.new
@host, @port, @select = host, port, select
@quit, @queue = false, []
end
def closed?
@sock.nil? || @sock.closed?
end
def disconnect
@sock.close unless @sock.nil?
end
alias :close :disconnect
def connect
@sock = VDR::Socket.new :sock => TCPSocket.new( serv, port), :select => @select, :parent => self
@answer = FirstLine.new
rescue Errno::ECONNREFUSED
retry
end
def event_answer line
# Kernel.debug "@answer=#{@answer}"
l = /^(\d\d\d)([ -])(.*?)[\n\r]*$/.match line
if l.nil?
# Kernel.debug "i don't understand this line: #{line}"
return
elsif l[ 1].to_i == 221
else
@answer.client.puts l[ 1..-1].to_s
self.next true if l[ 2] == ' '
end
end
def event_client_closed client
@queue.unshift @answer unless @answer.nil?
@firstline = @sock = nil
self.next unless @queue.empty?
end
def next clear_answer = false
# Kernel.debug "@queue = [#{@queue.collect{|i|i.to_s}.join ", "}]; @answer = #{@answer.inspect}"
@answer = nil if clear_answer
return self.close if @quit
return @answer if @answer
begin
@answer = @queue.shift
end while !@answer.nil? && @answer.client.closed?
if @answer.nil?
elsif self.closed?
@queue.unshift @answer
self.connect
else
@sock.puts @answer.str
end
@answer
end
def push client, str
r = Request.new client, str.strip
raise "Not a valid String: #{r.str.inject}" if !r.str.kind_of?( String) || r.str.empty?
@queue.unshift r
self.next
end
def quit
unless self.closed?
q = Class.new
class <<q
def client; self; end
def closed?; false; end
def str; "quit"; end
end
@queue.unshift q
self.next
end
@quit = true
self.closed?
end
end
class VDR::Socket <Select::Socket
def initialize opts
opts.update( :delimiter => /\r?\n/)
super opts
end
def event_line line
@parent.event_answer line
end
def quit
@sock.puts "quit"
end
end
class VDR::FirstLine
attr_reader :line, :client, :str
def initialize
@client = self
@str = nil
end
def write line
@line = line
end
alias :print :write
alias :puts :write
alias :to_s :line
end
###############################################################################
# debug #######################################################################
###############################################################################
if %W{-D --debug}.include? ARGV[0]
ARGV.shift
$DEBUG = true
end
$DEBUG = true if ENV['DEBUG']
if $DEBUG
def debug_func c, f
ff = case f
when /^(.*)\?$/ then "#{$1}_f"
when /^(.*)\!$/ then "#{$1}_a"
when "<<" then "_s"
when "+" then "_p"
when "-" then "_m"
when "@+" then "_P"
when "@-" then "_M"
else "#{f}_n"
end
wf = "__wrapped_#{c.object_id.to_s.sub /^-/, "x"}_#{ff}__".intern
return "#{c}##{wf} already exists" if c.instance_methods.include? wf
pre = "\#{\"%x\"%self.hash.abs}:#{c}##{f}"
c.class_eval <<-EOF
alias :#{wf} :#{f}
def #{f} *args, &e
ret = if e
STDERR.puts "==>#{pre} \#{args.collect {|i| i.inspect }.join ", "}, &\#{e.inspect}"
#{wf} *args, &e
else
STDERR.puts "==>#{pre} \#{args.collect {|i| i.inspect }.join ", "}"
#{wf} *args
end
#STDERR.puts "<==#{pre}"
ret
rescue
STDERR.puts "<==#{pre} EXCEPTION: \#{$!.inspect}"
Kernel.raise
end
EOF
end
def debug_class c, fs
c.instance_methods.grep fs do |f|
debug_func c, f
end
end
debug_class Select, /_set|_del$/
debug_class Select::Socket, /^event_.*|write|print|init|close$/
debug_class Select::Server, /^event_.*|close|init$/
debug_class VDR, /^event_.*|next|push|connect|close|disconnect$/
debug_class VDR::Socket, /^event_.*|close|init$/
debug_class SVDRPD, /^event_.*|init$/
debug_class SVDRPC, /^event_.*|init$/
debug_class VDR::FirstLine, /^write|print|puts$/
end
###############################################################################
# main ########################################################################
###############################################################################
opts = {
:vdraddr => ARGV[0] || 'localhost',
:vdrport => ARGV[1] || 2002,
:servaddr => ARGV[2] || 'localhost',
:servport => ARGV[3] || 2001
}
Syslog.open 'svdrpd', Syslog::LOG_NDELAY | Syslog::LOG_PERROR,
Syslog::LOG_DAEMON
begin
$select = Select.new
$select.timeout 5*60
$vdr = VDR.new opts[ :vdraddr], opts[ :vdrport], $select
$serv = SVDRPD.new :vdr => $vdr, :sock => TCPServer.new( opts[ :servaddr], opts[ :servport]), :select => $select
$select.exit_on_empty = true
$serv.run
rescue Object
Syslog.err "#{$!} (#{$!.class}) -- #{$!.backtrace.join ' -- '}"
$serv.quit "server shuting down"
$vdr.quit
retry
end

44
svdrpd.gemspec Normal file
View file

@ -0,0 +1,44 @@
# Generated by jeweler
# DO NOT EDIT THIS FILE DIRECTLY
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
# -*- encoding: utf-8 -*-
Gem::Specification.new do |s|
s.name = %q{svdrpd}
s.version = "0.1.0"
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["Denis Knauf"]
s.date = %q{2010-02-26}
s.default_executable = %q{svdrpd}
s.description = %q{Allowes to more than one connection to communicate with VDR}
s.email = %q{Denis.Knauf@gmail.com}
s.executables = ["svdrpd"]
s.extra_rdoc_files = [
"README.md"
]
s.files = [
"README.md",
"VERSION",
"bin/svdrpd"
]
s.homepage = %q{http://github.com/DenisKnauf/svdrpd}
s.rdoc_options = ["--charset=UTF-8"]
s.require_paths = ["bin"]
s.rubygems_version = %q{1.3.5}
s.summary = %q{Multiplexer for SVDRP}
if s.respond_to? :specification_version then
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
s.specification_version = 3
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
s.add_runtime_dependency(%q<select>, [">= 0"])
else
s.add_dependency(%q<select>, [">= 0"])
end
else
s.add_dependency(%q<select>, [">= 0"])
end
end