#! %%PREFIX%%/bin/ruby # # ck4up # # Copyright (c) Jürgen Daubert # Version 0.2.4 2007-09-25 # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, # USA. # require 'net/http' require 'net/ftp' require 'md5' require 'gdbm' require 'getoptlong' require 'timeout' require 'resolv-replace' BaseDir = ENV['HOME'] + '/.ck4up/' $Config = BaseDir + 'ck4up.conf' $Database = BaseDir + 'ck4up.dbm' Threads_max = 20 # Number of parallel threads Ftp_passive = true # use passive mode for ftp Ftp_time = 20 # timeout in seconds for ftp requests def usage() print < exception puts "Failed to create data directory #{BaseDir}: " + exception exit -1 end end end class Parser def initialize(file) @file = file @macro = {} Parser.exist_config(@file) end def Parser.exist_config(file) if not FileTest::exist?(file) puts "Configuration file #{file} not found !" puts "Please create one, before using ck4up." exit -1 end end def parse File.read(@file).each do |row| yield replace_macros(row) end end def replace_macros(line) case line when /^@\w*@/ @macro[line.split[0]] = line.split[1..-1].join(' ') return false when /^[a-zA-Z]/ name = line.split[0] expand_macros(line) return line.gsub('@NAME@',name) else return false end end def expand_macros(line) @macro.each { |k,v| expand_macros(line) if line.gsub!(k,v) } end end class CheckUp SAME = 0 DIFF = 1 NEW = 2 def CheckUp.set_db(db,access) @@db_readonly = access if FileTest::exist?(db) @@db = GDBM.open(db) else puts "Creating new database #{db}" @@db = GDBM.new(db) end at_exit { @@db.close } end def check(name,type,url=nil,regexp=nil) @name = name @url=url case type when 'md5' then check_md5(regexp) else raise "Unknown type: #{type}" end end def check_md5(reg) page = fetch_page page = page.scan(/#{reg}/).uniq if reg puts page if Opts["debug"] page = page.to_s raise "empty result" if page == "" save_md5(MD5.new(page).hexdigest) end def save_md5(md5) if md5 == @@db[@name] return SAME else @@db[@name] ? res = DIFF : res = NEW @@db[@name] = md5 if not @@db_readonly return res end end def clean_db(keeplist) active = Hash.new for k in keeplist active[k] = @@db[k] end oldCount = @@db.size @@db.clear active.each_key do |key| @@db[key] = active[key] if active[key] end return oldCount - @@db.size end def fetch_page() begin uri = URI.parse(@url) case uri.scheme when "http" Net::HTTP.get(uri) when "ftp" Timeout::timeout(Ftp_time) do ftp = Net::FTP.new(uri.host) ftp.passive = true if Ftp_passive ftp.login("anonymous", "ck4up@example.com") ftp.chdir(uri.path) res = ftp.list.join("\n") ftp.close return res end else raise "wrong url syntax #{@url}" end rescue Exception => err raise err.to_s end end end trap('INT') { puts; exit } threads = [] create_basedir Opts = parse_options Parser.exist_config($Config) CheckUp.set_db($Database,Opts["keep"]) if Opts["cleandb"] checkUp = CheckUp.new keeplist = [] Parser.new($Config).parse do |line| if line n,t,u,r = line.split keeplist.push(n) end end count = checkUp.clean_db(keeplist) printf "Removed %d records\n", count exit else Parser.new($Config).parse do |line| while Thread.list.size > Threads_max; sleep 1; end if line and line.index(/#{ARGV.join('|')}/) n,t,u,r = line.split threads << Thread.new(n,t,u,r) do |name,type,url,regexp| begin result = CheckUp.new.check(name,type,url,regexp) case result when CheckUp::SAME print_result(STDOUT,name,'ok') if Opts["verbose"] when CheckUp::NEW print_result(STDOUT,name,'new: ',url) else print_result(STDOUT,name,'diff:',url) end rescue => error print_result(STDERR,name,'error:',error.to_s.strip) end end end end threads.each { |t| t.join } end # vim:ts=2 # End of file