From 54a825211ee18cd025adbabd200c4f473c2ab5a8 Mon Sep 17 00:00:00 2001 From: Denis Knauf Date: Fri, 26 Feb 2010 02:14:41 +0100 Subject: [PATCH] 0.1.0 --- AUTHOR | 1 + README.md | 0 Rakefile | 58 +++++++++++ VERSION | 1 + bin/svdrpd | 263 +++++++++++++++++++++++++++++++++++++++++++++++++ svdrpd.gemspec | 44 +++++++++ 6 files changed, 367 insertions(+) create mode 100644 AUTHOR create mode 100644 README.md create mode 100644 Rakefile create mode 100644 VERSION create mode 100755 bin/svdrpd create mode 100644 svdrpd.gemspec diff --git a/AUTHOR b/AUTHOR new file mode 100644 index 0000000..50824fb --- /dev/null +++ b/AUTHOR @@ -0,0 +1 @@ +Denis Knauf diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..0205eea --- /dev/null +++ b/Rakefile @@ -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 diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..6e8bf73 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.1.0 diff --git a/bin/svdrpd b/bin/svdrpd new file mode 100755 index 0000000..f2490a4 --- /dev/null +++ b/bin/svdrpd @@ -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 /\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 @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 < /\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 diff --git a/svdrpd.gemspec b/svdrpd.gemspec new file mode 100644 index 0000000..d6783e7 --- /dev/null +++ b/svdrpd.gemspec @@ -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, [">= 0"]) + end + else + s.add_dependency(%q