#!/usr/bin/ruby
#
# apt-listbugs: retrieves bug reports and lists them
#
# Copyright (C) 2002       Masato Taruishi <taru@debian.org>
# Copyright (C) 2006-2008  Junichi Uekawa <dancer@debian.org>
# Copyright (C) 2007       Famelis George <famelis@otenet.gr>
# Copyright (C) 2008-2024  Francesco Poli <invernomuto@paranoici.org>
# Copyright (C) 2009       Ryan Niebur <ryan@debian.org>
# Copyright (C) 2012       Justin B Rye <jbr@edlug.org.uk>
# Copyright (C) 2013       Google Inc
# Copyright (C) 2015       Michael Gold <michael@bitplane.org>
#
#  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 with
#  the Debian GNU/Linux distribution in file /usr/share/common-licenses/GPL-2;
#  if not, write to the Free Software Foundation, Inc.,
#  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
#


# exit gracefully when user presses [Ctrl+C]
Signal.trap("SIGINT") { $stderr.puts "Interrupted"; exit 130 }

# exit successfully when SIGUSR1 is received
Signal.trap("SIGUSR1") { $stderr.puts "Emergency exit"; exit! 0 }


if File.expand_path(__FILE__).match(/^\/usr\/s?bin\//)
  $VERSION = `dpkg-query -W -f='${Version}' apt-listbugs`
else
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "../lib"))
  $VERSION = `dpkg-parsechangelog -SVersion`
end

require 'gettext'
include GetText

GetText::bindtextdomain("apt-listbugs")

begin
  require 'debian'
  require 'unicode'
rescue LoadError
  # TRANSLATORS: "E: " is a label for error messages; you may translate it with a suitable abbreviation of the word "error"
  $stderr.puts _("E: ") + "#{$!}"
  $stderr.puts _("This may be caused by a package lacking support for the ruby interpreter in use. Try to fix the situation with the following commands:")
  $stderr.puts "  # mv /etc/apt/apt.conf.d/10apt-listbugs /root/"
  $stderr.puts "  # aptitude update"
  $stderr.puts "  # aptitude install ruby-debian ruby-unicode ruby"
  $stderr.puts "  # mv /root/10apt-listbugs /etc/apt/apt.conf.d/"
  exit 1
end
require 'aptlistbugs/logic'

## main from here

# Drop out as early as possible if this env var is set.
if ENV["APT_LISTBUGS_FRONTEND"] == "none"
  exit 0
end
# handle options
config = AppConfig.new
config.parse_options
Factory.config = config

# handle arguments
new_pkgs = {}
cur_pkgs = {}
native_arch = nil
case config.command
when "apt"
  # parse APT VERSION 3 input.
  state=1
  apt_hook_fd = ENV["APT_HOOK_INFO_FD"]
  config.debugfile.puts if $DEBUG
  config.debugfile.puts "Preparing to read info from file descriptor \"#{apt_hook_fd}\"" if $DEBUG
  if apt_hook_fd.nil?
    $stderr.print _("E: ") + _("APT_HOOK_INFO_FD is undefined.\n")
    exit 1
  end
  apt_hook_fd = apt_hook_fd.to_i
  if apt_hook_fd == 0
    $stderr.print _("E: ") + _("APT_HOOK_INFO_FD is not correctly defined.\n")
    exit 1
  end
  begin
    apt_hook_stream = IO.open(apt_hook_fd, 'r')
  rescue Errno::ENOENT, Errno::EBADF
    $stderr.puts _("E: ") + sprintf(_("Cannot read from file descriptor %d"), apt_hook_fd)
    exit 1
  end
  config.debugfile.puts if $DEBUG
  config.debugfile.puts "Pre-Install-Pkgs hook info:" if $DEBUG
  apt_hook_stream.each { |pkg|
    pkg=pkg.rstrip
    case state
    when 1
      # the version header, only one line.
      if pkg == "VERSION 3"
        config.debugfile.puts "#{pkg}" if $DEBUG
        state=2
      else
        $stderr.print _("E: ") + _("APT Pre-Install-Pkgs failed to provide the expected 'VERSION 3' string.\n")
        exit 1
      end
    when 2
      # APT configuration lines
      case pkg
      when ""
        config.debugfile.puts "#{pkg}" if $DEBUG
        state=3
      when /^APT::Architecture=(.*)/
        if $1
          config.debugfile.puts "#{pkg}" if $DEBUG
          native_arch=$1
        end
      when /^quiet=(.*)/
        if $1.to_i > 0
          config.debugfile.puts "#{pkg}" if $DEBUG
          config.quiet=true
        end
      end
    when 3
      # package action lines
      config.debugfile.puts "#{pkg}" if $DEBUG
      pkg_name, old_ver, old_arch, old_ma, direction, new_ver, new_arch, new_ma, filename = pkg.split(" ")
      case filename
      when "**CONFIGURE**"
        # none
      when "**REMOVE**"
        # none
      when nil
        $stderr.print _("E: ") + _("APT Pre-Install-Pkgs provided fewer fields than expected.\n")
        exit 1
      else
        case direction
        when "="
          # no version change, hence no new bug can be introduced
          # into the system by this package
        when ">", "<"
          # ">" means downgrade, "<" means upgrade
          if ( config.show_downgrade or direction == "<" )
            if ( pkg_name != nil and new_ver != "-" )
              f = {}
              f["package"] = pkg_name
              f["version"] = new_ver
              # pkg_key is the package full name (<pkg_name> for native and
              # "all" architecture packages, <pkg_name>:<arch> for foreign
              # architecture packages)
              pkg_key = pkg_name
              if ( new_arch != nil and new_arch != "all" and new_arch != native_arch )
                pkg_key = pkg_name + ":" + new_arch
              end
              new_pkgs[pkg_key] = f
              if ( old_ver != "-" )
                f = {}
                f["package"] = pkg_name
                f["version"] = old_ver
                cur_pkgs[pkg_key] = f
              end
            end
          end
        else
          $stderr.print _("E: ") + _("APT Pre-Install-Pkgs provided an invalid direction of version change.\n")
          exit 1
        end
      end
    end
  }
  apt_hook_stream.close
  config.debugfile.puts if $DEBUG
when "list", "rss"
  ARGV.each { |pkg_key|
    # parse 'apt-listbugs list pkg_name:arch/version ... ' combination
    if ( pkg_key != nil )
      f = {}
      if /^(.*)\/(.*)$/ =~ pkg_key
        pkg_key = $1
        f["version"] = $2
      end
      if /^(.*):(.*)$/ =~ pkg_key
        f["package"] = $1
      else
        f["package"] = pkg_key
      end
      new_pkgs[pkg_key] = f
    end
  }
end

exit 0 if new_pkgs.size == 0

Factory::BugsFactory.delete_ignore_pkgs(new_pkgs) if config.command == "apt"

exit 0 if new_pkgs.size == 0

# build the multiarch map: for each pkg_name, list the corresponding pkg_keys
ma_copies = {}
new_pkgs.each_pair { |pkg_key, pkg|
  pkg_name = pkg["package"]
  if ( pkg_name != nil )
    ma_copies[pkg_name] = [] if ma_copies[pkg_name] == nil
    ma_copies[pkg_name] << pkg_key
  end
}

# read bug reports
begin
  bugs = Factory::BugsFactory.create(ma_copies) { |msg, val|
    config.frontend.progress(msg, val) if config.quiet == false
  }
rescue
  $stderr.puts _("E: ") + "#{$!}"
  exit 1
end

Factory::BugsFactory.delete_ignore_bugs(bugs) if config.command == "apt"
Factory::BugsFactory.delete_regexp_bugs(bugs, config.ignore_regexp) if config.command == "apt" and config.ignore_regexp
Factory::BugsFactory.delete_uninteresting_bugs(bugs) if config.fbugs
Factory::BugsFactory.delete_unwanted_tag_bugs(bugs) if config.tag
Factory::BugsFactory.delete_not_for_us_bugs(bugs) if config.distro
begin
  Factory::BugsFactory.delete_irrelevant_bugs(bugs, cur_pkgs, new_pkgs) { |msg, val|
    config.frontend.progress(msg, val) if config.quiet == false
  }
rescue
  $stderr.puts _("E: ") + "#{$!}"
  exit 1
end

exit 0 if config.command != "rss" && bugs.size == 0

# read done. now starting viewer
viewer = nil
case config.command
when "apt"
  viewer = Viewer::SimpleViewer.new(config)
when "list"
  viewer = Viewer::SimpleViewer.new(config)
when "rss"
  viewer = Viewer::RSSViewer.new(config)
end
if viewer.view(new_pkgs, cur_pkgs, bugs) == false
  ErrorWarning =  _("****** Exiting with an error in order to stop the installation. ******")
  ErrorWarningHeader = "*" * Unicode.width(ErrorWarning)
  config.frontend.puts ErrorWarningHeader
  config.frontend.puts ErrorWarning
  config.frontend.puts ErrorWarningHeader
  config.frontend.close
  exit 10
end
config.frontend.close
