yast/yast-yast2

View on GitHub
library/packages/src/lib/packages/file_conflict_callbacks.rb

Summary

Maintainability
A
0 mins
Test Coverage
# ------------------------------------------------------------------------------
# Copyright (c) 2016-2022 SUSE LLC
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of version 2 of the GNU General Public License as published by the
# Free Software Foundation.
#
# 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.
#
# ------------------------------------------------------------------------------
#

require "yast"
require "ui/delayed_progress_popup"

module Packages
  # Default file conflicts callbacks for package bindings. To register the
  # callbacks in Yast::Pkg just call {Packages::FileConflictCallbacks.register}
  class FileConflictCallbacks
    class << self
      include Yast::Logger
      include Yast::I18n
      include Yast::UIShortcuts

      # register the file conflict callbacks
      def register
        Yast.import "Pkg"
        Yast.import "UI"
        Yast.import "Progress"
        Yast.import "Mode"
        Yast.import "CommandLine"
        Yast.import "Report"
        Yast.import "Label"
        Yast.import "PackageCallbacks"
        Yast.import "Wizard"

        textdomain "base"

        create_delayed_progress_popup unless Yast::Mode.commandline
        register_file_conflict_callbacks
      end

    private

      # Helper function for creating a YaST function reference
      def fun_ref(*args)
        Yast::FunRef.new(*args)
      end

      # Register the default file conflicts callbacks
      def register_file_conflict_callbacks
        log.info "Registering file conflict callbacks"

        Yast::Pkg.CallbackFileConflictStart(fun_ref(method(:start), "void ()"))
        Yast::Pkg.CallbackFileConflictProgress(fun_ref(method(:progress),
          "boolean (integer)"))
        Yast::Pkg.CallbackFileConflictReport(fun_ref(method(:report),
          "boolean (list<string>, list<string>)"))
        Yast::Pkg.CallbackFileConflictFinish(fun_ref(method(:finish), "void ()"))

        nil
      end

      # Create the DelayedProgressPopup, but don't start the timer yet.
      #
      # This needs to be done outside of any of the callbacks to avoid clashes
      # between Ruby, C++ and ex-YCP YaST components.
      #
      # rubocop:disable Style/ClassVars
      def create_delayed_progress_popup
        log.info "Creating DelayedProgressPopup"
        @@delayed_progress_popup ||= Yast::DelayedProgressPopup.new(
          auto_start: false,
          heading:    progress_bar_label
        )
        nil
      end

      def delayed_progress_popup
        # Intentionally NOT creating the DelayedProgressPopup here so it isn't
        # accidentially created in one of the callbacks.
        @@delayed_progress_popup
      end
      # rubocop:enable Style/ClassVars

      def progress_bar_label
        # TRANSLATORS: progress bar label
        _("Checking file conflicts...")
      end

      # Set the label text of the global progress bar, if it exists.
      #
      # @param [String] text
      #
      def update_progress_text(text)
        # This uses the widget ID of the global progress bar of the SlideShow
        # module directly to keep the cross-dependencies down at least a little
        # bit.
        #
        # On the plus side, this is so defensive (it does nothing if there is
        # no such widget) that the worst thing that can happen is that there is
        # that there is no immediate feedback by setting the progress bar label.
        return unless Yast::UI.WidgetExists(:progressTotal)

        Yast::UI.ChangeWidget(:progressTotal, :Label, text)
      end

      # Handle the file conflict detection start callback.
      def start
        log.info "Starting the file conflict check..."

        if Yast::Mode.commandline
          Yast::CommandLine.PrintVerbose(progress_bar_label)
        else
          # Don't create the DelayedProgressPopup here, otherwise there will be
          # conflicts between the Ruby, C++ and ex-YCP YaST components.
          delayed_progress_popup.start_timer
          update_progress_text(progress_bar_label) # Immediate feedback
        end
      end

      # Handle the file conflict detection progress callback.
      # @param [Fixnum] progress progress in percents
      # @return [Boolean] true = continue, false = abort
      def progress(progress)
        log.debug "File conflict progress: #{progress}%"

        if Yast::Mode.commandline
          Yast::CommandLine.PrintVerboseNoCR("#{Yast::PackageCallbacksClass::CLEAR_PROGRESS_TEXT}#{progress}%")
          return true
        end

        delayed_progress_popup.progress(progress)
        return true unless delayed_progress_popup.open?

        ui = Yast::UI.PollInput
        log.info "User input in file conflict progress (#{progress}%): #{ui}"

        ui != :abort && ui != :cancel
      end

      # Handle the file conflict detection result callback.
      # Ask the user whether to continue. In AutoYaST mode, an error is
      # reported, but the installation will continue ignoring the conflicts.
      #
      # @param excluded_packages [Array<String>] packages ignored in the check
      #   (e.g. not available for check in the download-as-needed mode)
      # @param conflicts [Array<String>] list of translated descriptions of
      #   the detected file conflicts
      # @return [Boolean] true = continue, false = abort
      #
      def report(excluded_packages, conflicts)
        log.info "Excluded #{excluded_packages.size} packages in file conflict check"
        log.debug "Excluded packages: #{excluded_packages.inspect}"
        log.info "Found #{conflicts.size} conflicts: #{conflicts.join("\n\n")}"

        # just continue installing packages if there is no conflict
        return true if conflicts.empty?

        # don't ask in autoyast or command line mode, just report/log the issues and continue
        if Yast::Mode.auto || Yast::Mode.commandline
          # TRANSLATORS: An error message, %s is the actual list of detected conflicts
          Yast::Report.Error(_("File conflicts detected, these conflicting files will " \
                               "be overwritten:\n\n%s") % conflicts.join("\n\n"))
          return true
        end

        Yast::UI.OpenDialog(dialog(conflicts))

        begin
          ret = Yast::UI.UserInput
          log.info "User Input: #{ret}"
          ret == :continue
        ensure
          Yast::UI.CloseDialog
        end
      end

      # Handle the file conflict detection finish callback.
      def finish
        log.info "File conflict check finished"
        return if Yast::Mode.commandline

        delayed_progress_popup.close
        update_progress_text(" ") # One blank to maintain the label's height
      end

      # Construct the file conflicts dialog.
      # @param [Array<String>] conflicts file conflicts reported by libzypp
      #   (in human readable form)
      # @return [Term] UI term
      def dialog(conflicts)
        button_box = ButtonBox(
          PushButton(Id(:continue), Opt(:okButton), Yast::Label.ContinueButton),
          PushButton(Id(:abort), Opt(:default, :cancelButton), Yast::Label.AbortButton)
        )

        # TRANSLATORS: A popup label, use max. 70 chars per line, use more lines if needed
        label = _("File conflicts happen when two packages attempt to install\n" \
                  "files with the same name but different contents. If you continue\n" \
                  "the conflicting files will be replaced, losing the previous content.")

        # TRANSLATORS: Popup heading
        heading = n_("A File Conflict Detected", "File Conflicts Detected", conflicts.size)

        VBox(
          Left(Heading(heading)),
          VSpacing(0.2),
          Left(Label(label)),
          MinSize(65, 15, RichText(Opt(:plainText), conflicts.join("\n\n"))),
          button_box
        )
      end
    end
  end
end