#!/usr/bin/ruby -w PVERSION = '2.5.2' #author cyryl #license GPL3 # downloading rpms locally require 'progressbar' require 'open-uri' require 'ftools' # makedirs require 'bz2' require 'stringio' require 'digest/md5' require 'gettext' # command line options require 'optparse' require 'ostruct' # program data directory PROGRAM_DIR = "/var/chk_update" NO_UPDATES_ICON = 'chk_update-no_updates.png' UPDATES_ICON = 'chk_update-new.png' GetText.bindtextdomain('check_update', {:path => "#{PROGRAM_DIR}/langs/"}) # program runtime options @options = OpenStruct.new @options.gui = :kdialog; # :cm (command line) | :kdialog # # ============= PARSE COMMAND LINE ARGS ======================= # opts = OptionParser.new do |opts| opts.banner = "Usage: /var/chk_update/chk_update.rb [options]" opts.on('-g', '--gui', "Use GUI based on kdialog") do @options.gui = :kdialog end opts.on("-c", "--no-gui", "Command line GUI") do @options.gui = :cm end opts.on("-n", "DEPRECATED Command line GUI") do puts "DEPRECATED OPTION: -n. USE -c" @options.gui = :cm end opts.on("-q", "--quiet", "Quiet mode, just update desktop icon.", "Don't start Synaptic and don't ask about anything") do @options.gui = :quiet end opts.on('-u', '--update', "Update system (using upt-get dist-upgrade)") do `xterm -T 'System update' -e "apt-get update; mv #{PROGRAM_DIR}/archives/*.rpm /var/cache/apt/archives/; rm -f #{PROGRAM_DIR}/rb_cache/*; apt-get dist-upgrade"` exit 0 end opts.on('-s', '--start-synaptic', "DEPRECATED. Use --update") do puts "DEPRECATED OPTION: -s. USE --update" `xterm -T 'System update' -e "apt-get update; mv #{PROGRAM_DIR}/archives/*.rpm /var/cache/apt/archives/; rm -f #{PROGRAM_DIR}/rb_cache/*; apt-get dist-upgrade"` exit 0 end opts.on_tail('--version', "Display version number") do puts "chk_update.rb version #{PVERSION}" exit end opts.on_tail("-h", "--help", "Show this help") do puts opts exit end end begin opts.parse! rescue => ex puts ex.message exit end # apt-cache options builder apt_opts = "-o Dir::State::Lists='#{PROGRAM_DIR}/lists' -o Dir::Cache='#{PROGRAM_DIR}/cache'" # require class Package require "#{PROGRAM_DIR}/lib/package.rb" require "#{PROGRAM_DIR}/lib/repository.rb" # # GUI CLASSESS # require "#{PROGRAM_DIR}/lib/gui/gui.rb" require "#{PROGRAM_DIR}/lib/gui/commandline.rb" require "#{PROGRAM_DIR}/lib/gui/dcop.rb" require "#{PROGRAM_DIR}/lib/gui/qdbus.rb" # @param string url # @return HTTP::response def download(url) pbar = nil url = url.gsub(/([^:])\/{2,}/, '\1/') opts = { :content_length_proc => lambda {|t| if t && 0 < t pbar = ProgressBar.new(File.basename(url), t) pbar.file_transfer_mode end }, :progress_proc => lambda {|s| pbar.set s if pbar } } uri = URI.split(url) opts[:http_basic_authentication] = [uri[1].split(':')[0], uri[1].split(':')[1]] if not uri[1].nil? r = open(url, opts) pbar.finish pbar.clear r.read end # @param string url package url # @param string /path/to/saved/file # @param GUI global gui object, temporary workaround def downloadPkg(url, savepath, gui) if File.file?(savepath) printf "\033[31mHit:\033[0m %s\n", File.basename(savepath) return end printf "\033[31mGet:\033[0m %s\n", File.basename(savepath) begin data = download(url) rescue puts "Download error: #{$!}" gui.downloadError else open(savepath, "wb") { |file| file.write(data) } end end def updateDesktopIcon(from, to) home = `echo -n ~` file = home + '/Desktop/check_update.desktop' data = File.readlines(file) fp = File.open(file+'~', 'w') data.each { |l| fp.write(l.gsub(from, to)) } fp.close File.move(file+'~', file) end # # OK, enough of definitions # GO to main program execution # count_repos = 0 repos = File.new('/etc/apt/sources.list', 'r').readlines.map{ |r| if r =~ /^rpm/ count_repos += r.split().size - 2 r end } case @options.gui when :kdialog then gui = QDBUS.new(GetText._("[%d / %d] Downloading package lists...") % [1, 5], count_repos, "Check Update 2") when :cm then gui = CommandLine.new(GetText._("[%d / %d] Downloading package lists...") % [1, 5], count_repos) when :quiet then gui = GUI.new end # to check, if there were errors downloading package lists. @@download_errors = false @repositories = [] repos.each do |r| next if r.nil? @repositories << Repository.new(r) end # download releases and sections @repositories.each do |r| r.get_release r.get_sections gui.next end gui.downloadError if @@download_errors # check, if there was any changes if @repositories.find_all{|r| r.changed?}.count == 0 # reposotries not updated. # cahce file exists? if File.file?(PROGRAM_DIR+'/rb_cache/update.dump') and File.file?(PROGRAM_DIR+'/rb_cache/new.dump') # read cache toUpdate = [] newpkgs = [] File.open(PROGRAM_DIR+'/rb_cache/update.dump') {|f| toUpdate = Marshal.load(f) } File.open(PROGRAM_DIR+'/rb_cache/new.dump') {|f| newpkgs = Marshal.load(f) } #display cached results gui.printResults(toUpdate, newpkgs) # display informations to start synaptic gui.finish(toUpdate.size > 0) # close and clean gui gui.close exit else puts "No chage files. Generating new ones." end else puts "Some repositories changed. Updating data." end gui.label = GetText._("[%d / %d] Generating caches and lists...") % [2, 5] gui.progress = 0 gui.steps = 3 gui.msg GetText._("Generating caches...") # generate cache `apt-cache gencaches #{apt_opts} --generate` gui.next # available installed packages # get this to other array to faster search avinstpkgs = [] countPackages = 0 #currently installed packages installedpkgs = [] gui.msg GetText._("Creating list of installed packages...") `rpm -qa --qf "%{NAME}\t%{epoch}\t%{version}-%{release}\n"`.split(/\n/).each { |line| arr = line.split pkg = Package.new pkg.package = arr[0] if arr[1] == '(none)' or arr[1] == "0" pkg.version = arr[2] else pkg.version = "#{arr[1]}:#{arr[2]}" end installedpkgs << pkg } gui.next gui.msg GetText._("Creating list of current packages...") currlist = `apt-cache pkgnames --no-generate`.split() newpkgs = [] reg_p = /^Package:\s*(.+)/ reg_s = /^Section:\s*(.+)/ reg_v = /^Version:\s*(.+)/ reg_f = /^Filename:\s*(.+)/ reg_u = /^Summary:\s*(.+)/ gui.label = GetText._("[%d / %d] Creating list of available packages...") % [3, 5] gui.progress = 0 dumpavail = `apt-cache dumpavail #{apt_opts} --no-generate`.split(/\n/) gui.steps = dumpavail.map{|p| p if p =~ reg_p}.nitems - 1 pkg = Package.new dumpavail.map { |line| line.chomp! if line.size == 0 and not pkg.package.nil? then if not currlist.include?(pkg.package) newpkgs << pkg elsif installedpkgs.include? pkg avinstpkgs << pkg end countPackages += 1 pkg = Package.new gui.next end pkg.package = line[reg_p, 1] if line =~ reg_p pkg.section = line[reg_s, 1] if line =~ reg_s pkg.version = line[reg_v, 1] if line =~ reg_v pkg.filename = line[reg_f, 1] if line =~ reg_f pkg.summary = line[reg_u, 1] if line =~ reg_u } gui.label = GetText._("[%d / %d] Searching for updates...") % [4, 5] gui.progress = 0 gui.steps = installedpkgs.size toUpdate = [] installedpkgs.each { |pkg| avinstpkgs.each { |apkg| if apkg > pkg gui.msg "\033[31mUpdate:\033[0m %s %s => %s", [pkg.package, pkg.version, apkg.version] toUpdate << apkg end } gui.next } gui.label = GetText._("[%d / %d] Getting changelogs for %d updates...") % [5, 5, toUpdate.size] gui.progress = 0 gui.steps = toUpdate.size if toUpdate.size > 0 dump = `apt-cache dump #{apt_opts} --no-generate`.split(/\n/) toUpdate.each { |pkg| line = dump.index("Package: #{pkg.package}") gui.next if line.nil? or dump[line+2].nil? or dump[line+2][/([\w\d\.]+\.[a-z]{2,4})_/, 1].nil? gui.msg "\033[32mPackage: %s -- not found\033[0m", [pkg.package] else repos.map {|r| if r =~ Regexp.new(dump[line+2][/([\w\d\.]+\.[a-z]{2,4})_/, 1]) repo = r.split rpm_url = repo[1] << repo[2] << '/RPMS.' << dump[line+2][/\.([\w\d]+)$/, 1] << '/' << pkg.filename downloadPkg rpm_url, PROGRAM_DIR+'/archives/'+pkg.filename, gui pkg.changelog = `rpm -q --changelog -p '#{PROGRAM_DIR}/archives/#{pkg.filename}'` end } end } end toUpdate.sort! newpkgs.sort! #save results to cache! File.open(PROGRAM_DIR+'/rb_cache/update.dump', 'w+') do |f| Marshal.dump(toUpdate, f) end File.open(PROGRAM_DIR+'/rb_cache/new.dump', 'w+') do |f| Marshal.dump(newpkgs, f) end gui.printResults(toUpdate, newpkgs) # display informations to start synaptic gui.finish(toUpdate.size > 0) # close and clean gui gui.close puts puts "Installed packages: " << avinstpkgs.size.to_s puts "All packages: " << countPackages.to_s puts "To update: " + toUpdate.size.to_s puts "New packages: " + newpkgs.size.to_s