#! %%PREFIX%%/bin/ruby
#
# ck4up
#
# Copyright (c) Jürgen Daubert <juergen.daubert@t-online.de>
# 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 <<EOS
Usage: ck4up [options] [exp ...]
Options:
-d, --debug debug mode, print fetched pages
-h, --help print this help message
-k, --keep keep md5 values, don't change database
-c, --cleandb clean unused keys from database
-v, --verbose verbose mode, show unchanged pages
-f file, --config file use configuration from file, see ck4up(1)
exp check only configuration lines matching exp
EOS
exit
end
def parse_options()
options = {}
begin
valid_options = GetoptLong.new(
["--debug", "-d", GetoptLong::NO_ARGUMENT],
["--keep", "-k", GetoptLong::NO_ARGUMENT],
["--verbose", "-v", GetoptLong::NO_ARGUMENT],
["--cleandb", "-c", GetoptLong::NO_ARGUMENT],
["--help", "-h", GetoptLong::NO_ARGUMENT],
["--config", "-f", GetoptLong::REQUIRED_ARGUMENT])
valid_options.each do |opt,arg|
case opt
when "--debug" then options["debug"] = true
when "--keep" then options["keep"] = true
when "--verbose" then options["verbose"] = true
when "--cleandb" then options["cleandb"] = true
when "--help" then usage
when "--config" then
$Config = BaseDir + arg + ".conf"
$Database = BaseDir + arg + ".dbm"
end
end
rescue
exit -1
end
return options
end
def print_result(io,a,b,*c)
io.printf("%-.15s %-s %-6s %s\n", a,"."*([15-a.length,0].max),b,c)
end
def create_basedir()
if not File.directory?(BaseDir) then
puts "Creating data directory #{BaseDir}"
begin
Dir.mkdir(BaseDir)
rescue SystemCallError => 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
syntax highlighted by Code2HTML, v. 0.9.1