require 'open3' require 'time' require 'net/http' # Mirror toolkit functionality and other shared stuff module MirrorToolkit RSYNC_TIMESTAMP_FILE = '/gentoo-portage/metadata/timestamp' DISTFILES_TIMESTAMP_FILE = '/distfiles/timestamp.mirmon' TYPE_RSYNC = 1 TYPE_DISTFILES = 2 module_function # Calls `rsync_cat` to fetch a file from rsync def rsync_cat(uri) rsync_cat = File.join(File.dirname(__FILE__), '..', 'bin', 'rsync-cat') stdin, stdout, stderr, wait_thr = Open3.popen3(rsync_cat, uri) stdin.close if wait_thr.value == 0 return stdout.gets else fail "Rsync call unsuccessful: '#{stderr.gets.chomp}'" end end def curl(uri) stdin, stdout, stderr, wait_thr = Open3.popen3('curl', '-m', '20', uri) stdin.close if wait_thr.value == 0 return stdout.gets else fail "curl call unsuccessful: '#{stderr.gets.chomp}'" end end # Fetches a URI from any of the Gentoo mirror types def remote_fetch(url) case url.scheme when 'rsync' rsync_cat(url.to_s) when 'http', 'https' Net::HTTP.get(url) when 'ftp' curl(url.to_s) else fail 'Unknown URI scheme.' end end # Tries to return a Time object for every kind of timestamp used on Gentoo mirrors def parse_timestamp(ts) if ts.numeric? Time.at(ts.to_i).utc else Time.parse(ts).utc end end # Returns a URI object where to find the timestamp for the given url and mirror type. def get_timestamp_url(url, type) mirror = URI(url) case mirror.scheme when 'rsync' if type == TYPE_RSYNC mirror.path = MirrorToolkit::RSYNC_TIMESTAMP_FILE elsif type == TYPE_DISTFILES mirror.path += MirrorToolkit::DISTFILES_TIMESTAMP_FILE end when 'http', 'https', 'ftp' mirror.path += MirrorToolkit::DISTFILES_TIMESTAMP_FILE end mirror end # Returns the number of seconds a mirror is lagging behind, or nil if it cannot be determined. def get_lag(url, type) Time.now.utc - parse_timestamp(remote_fetch(get_timestamp_url(url, type))) rescue nil end # Renders seconds as a nice human-printable string def humanize_seconds(secs) [[60, :seconds], [60, :minutes], [24, :hours], [1000, :days]].map do |count, name| if secs > 0 secs, n = secs.divmod(count) "#{n.to_i} #{name}" end end.compact.reverse.join(' ') end end class String def numeric? true if Float(self) rescue false end end