#!/usr/bin/ruby -w PVERSION = '2.5.1' #author cyryl #license GPL3 # downloading rpms locally require 'progressbar' require 'open-uri' require 'ftools' # makedirs require 'bz2' require 'stringio' require 'digest/md5' require 'gettext' # 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 = {} @options[:gui] = :kdialog; # :cm (command line) | :kdialog # # ============= PARSE COMMAND LINE ARGS ======================= # ARGV.each { |arg| case arg when '-v', '--version' then puts "Check Update version " + PVERSION puts "Author cyryl " exit 0 when '-h', '--help' then puts "Usage: chk_update [-s|-h|-v] | [-g|-n|-q]" puts "Available options:" puts " -h --help Display this help file" puts " -v --version Display version information" puts " -g --gui Use GUI based on kdialog" puts " -n --no-gui Command line version" puts " -q --quiet Quiet mode, just update desktop icon." puts " Don't start Synaptic and don't ask about anything" puts " -s --start-synaptic Copy rpm cache and start Synaptic" exit 0 when '-g', '--gui' then @options[:gui] = :kdialog when '-n', '--no-gui' then @options[:gui] = :cm when '-q', '--quiet' then @options[:gui] = :quiet when '-s', '--start-synaptic' then `xterm -T 'System update' -e "apt-get update; mv #{PROGRAM_DIR}/archives/*.rpm /var/cache/apt/archives/; apt-get dist-upgrade"` exit 0 else puts "Unknown option: #{arg}" end } # apt-cache options builder apt_opts = "-o Dir::State::Lists='#{PROGRAM_DIR}/lists' -o Dir::Cache='#{PROGRAM_DIR}/cache'" class Package attr_accessor :package, :version, :section, :filename, :summary, :changelog def ==(other) self.package == other.package end def >(other) self == other and self.version != other.version end def lastChangelog changelog = self.changelog[/^\*[^*\n]+\n([^*]*)/m, 1] return changelog.strip if not changelog.nil? self.changelog end end # # GUI CLASSESS # require "#{PROGRAM_DIR}/lib/gui/gui.rb" require "#{PROGRAM_DIR}/lib/gui/commandline.rb" require "#{PROGRAM_DIR}/lib/gui/dcop.rb" # @param string url # @return HTTP::response def download(url) pbar = nil 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 repo_url # @param string distribution # @param string source # @param string /path/to/saved/file # @param GUI global gui object, temporary workaround def downloadPkglist (repo_url, distribution, source, sums) url = repo_url + '/' + distribution + '/base/pkglist.' + source + '.bz2' save_as = PROGRAM_DIR+'/lists/'+repo_url[/^(\w{3,5}:\/\/)?(\w+(:[\w\d]+)?@)?([\w\d\-\.\/]+\w)\/*$/, 4].gsub('/', '_') + '_' + distribution.gsub(/\//, '_') + '_base_pkglist.' + source if File.file?(save_as) and File.size(save_as) == sums[1].to_i and Digest::MD5.hexdigest(File.read(save_as)) == sums[0] printf "\033[31mHit:\033[0m %s %s %s\n", repo_url.sub(/:\/\/.+@/, '://'), distribution, source return end printf "\033[31mGet:\033[0m %s %s %s\n", repo_url.sub(/:\/\/.+@/, '://'), distribution, source begin data = download(url) rescue puts "Download error: #{$!}" @@download_errors = true else bz2 = BZ2::Reader.new(StringIO.new(data)) open(save_as, "wb") { |file| file.write(bz2.read) } bz2.close end 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 = DCOP.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 repos.each { |r| next if r.nil? rr = r.split url = rr[1] + '/' + rr[2] gui.next printf "\033[31mGet:\033[0m %s %s %s\n", rr[1].sub(/:\/\/.+@/, '://'), rr[2], 'release' begin text = download(url+'/base/release') rescue puts "Download error: #{$!}" gui.downloadError else md5sums = {} text.each_line{ |l| if l =~ /[a-f\d]{32}/ md5sums[l.split[2][/pkglist\.(\w+)$/, 1]] = l.split end } rr[3, 99].each { |s| gui.next downloadPkglist rr[1], rr[2], s, md5sums[s] } end } gui.downloadError if @@download_errors 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 gui.printResults(toUpdate, newpkgs) # display informations to start synaptic if toUpdate.size > 0 or newpkgs.size > 0 gui.finish(true) else gui.finish(false) end # 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