yast/yast-storage-ng

View on GitHub
src/lib/y2partitioner/dialogs/unmount.rb

Summary

Maintainability
A
0 mins
Test Coverage
# Copyright (c) [2021] SUSE LLC
#
# All Rights Reserved.
#
# 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.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, contact SUSE LLC.
#
# To contact SUSE LLC about this file by physical or electronic mail, you may
# find current contact information at www.suse.com.

require "yast/i18n"
require "yast2/popup"
require "y2partitioner/unmounter"

module Y2Partitioner
  module Dialogs
    # Dialog for unmounting devices
    #
    # It is intended to be used by some actions, for example, when deleting or resizing a device.
    #
    # @note This is not a CWM dialog. It only consists on a convenient wrapper that invokes some popups.
    class Unmount
      include Yast::I18n

      # Constructor
      #
      # @param devices [Array<Y2Storage::Mountable>] devices to unmount
      # @param note [String, nil] option note to include in the dialog
      # @param allow_continue [Boolean] whether the dialog should allow to continue without unmounting
      def initialize(*devices, note: nil, allow_continue: true)
        textdomain "storage"

        @devices = devices.flatten
        @note = note
        @allow_continue = allow_continue
      end

      # Shows a popup asking for unmounting the devices
      #
      # The user can select to unmount, to continue without unmounting (see {#allow_continue?}), or to
      # cancel.
      #
      # The devices are unmounted when the user selects to unmount (see {#unmount_devices}). In case of
      # unmounting errors, the user is informed by means of another popup.
      #
      # @return [:finish, :cancel]
      def run
        # FIXME: This check is here to avoid changes in installer behaviour for GA. The dialog for
        #   unmounting devices will be only shown in normal mode. This check can be removed after GA.
        return :finish unless Yast::Mode.normal

        loop do
          case show
          when :unmount
            unmount_devices
            break :finish if errors.none?

            errors_popup
          when :continue
            break :finish
          when :cancel
            break :cancel
          end
        end
      end

      private

      # All the devices to unmount
      #
      # @return [Array<Y2Storage::Mountable>]
      attr_reader :devices

      # Optional note to include in the dialog
      #
      # @return [String, nil]
      attr_reader :note

      # Whether to continue without unmounting is allowed
      #
      # @return [Boolean]
      attr_reader :allow_continue
      alias_method :allow_continue?, :allow_continue

      # Errors when trying to unmount devices
      #
      # @return [Array<String>]
      def errors
        @errors ||= []
      end

      # Devices successfully unmounted
      #
      # @return [Array<Y2Storage::Mountable>]
      def unmounted_devices
        @unmounted_devices ||= []
      end

      # Devices pending to be unmounted
      #
      # @return [Array<Y2Storage::Mountable>]
      def mounted_devices
        devices - unmounted_devices
      end

      # Unmounts the devices
      #
      # Errors are stored when some devices cannot be unmounted, see {#errors}.
      def unmount_devices
        @errors = []

        mounted_devices.each { |d| unmount_device(d) }
      end

      # Unmounts a given device
      #
      # @see Unmounter
      #
      # An error is stored when the device cannot be unmounted.
      #
      # @param device [Y2Storage::Mountable]
      def unmount_device(device)
        unmounter = Unmounter.new(device)
        unmounter.unmount

        if unmounter.error?
          errors << unmounter.error
        else
          unmounted_devices << device
        end
      end

      # Shows the popup asking for unmounting devices
      #
      # @return [Symbol]
      def show
        Yast2::Popup.show(content, headline: Yast::Label.WarningMsg, buttons: buttons, focus: :cancel)
      end

      # Content for the popup
      #
      # It includes information about the mounted devices and an optional note, see {#note}.
      #
      # @return [String]
      def content
        # TRANSLATORS: %{mount_sentences} is replaced by a text describing which devices are mounted.
        #   Try to keep line breaks.
        text = format(_("The following devices are currently mounted:\n\n%{mount_sentences}\n\n"),
          mount_sentences: mount_sentences)

        text << (note + "\n\n") if note

        text <<
          if allow_continue?
            _("You can try to unmount now, continue without unmounting or cancel.\n" \
              "Click Cancel unless you know exactly what you are doing.")
          else
            _("You can try to unmount now or cancel.\n" \
              "Click Cancel unless you know exactly what you are doing.")
          end

        text
      end

      # Sentences describing the mounted devices, each sentence in a separate line.
      #
      # @return [String]
      def mount_sentences
        mounted_devices.map { |d| mount_sentence(d) }.join("\n")
      end

      # Sentence describing a mounted device
      #
      # @param device [Y2Storage::Mountable]
      # @return [String]
      def mount_sentence(device)
        # TRANSLATORS: %{device} is replaced by a device name (e.g., /dev/sda1) and %{mount_point}
        #   is replaced by a mount path (e.g., /home).
        format(_("%{name} mounted at %{mount_point}"),
          name:        device_name(device),
          mount_point: device.mount_point.path)
      end

      # Generates the label for a given device
      #
      # @param device [Y2Storage::Mountable]
      # @return [String]
      def device_name(device)
        return btrfs_subvolume_name(device) if device.is?(:btrfs_subvolume)

        device = device.blk_devices.first if device.is?(:blk_filesystem) && !device.multidevice?

        device.display_name
      end

      # Generates the label for a Btrfs subvolume
      #
      # @param subvolume [Y2Storage::BtrfsSubvolume]
      # @return [String]
      def btrfs_subvolume_name(subvolume)
        # TRANSLATORS: %{path} is replaced by a Btrfs subvolume path (e.g., @home).
        format(_("Btrfs subvolume %{path}"), path: subvolume.path)
      end

      # Buttons to show in the dialog
      #
      # @return [Hash<Symbol, String>]
      def buttons
        buttons = {}

        buttons[:continue] = Yast::Label.ContinueButton if allow_continue?
        buttons[:cancel] = Yast::Label.CancelButton
        buttons[:unmount] = _("Unmount")

        buttons
      end

      # Shows a popup to inform about unmounting errors
      def errors_popup
        # TRANSLATORS: %{mount_sentences} is replaced by a text describing which devices are mounted.
        #   Try to keep line breaks.
        content = format(_("Some devices cannot be unmounted: \n\n%{mount_sentences}"),
          mount_sentences: mount_sentences)

        details = errors.join("\n\n-------\n\n")

        Yast2::Popup.show(content, headline: :error, details: details, buttons: :ok)
      end
    end
  end
end