Ddeepin-ci-robotchore: init
781dfa83创建于 2023年9月8日历史提交
/*
 * RestoreDeviceBox.vala
 *
 * Copyright 2012-2018 Tony George <teejeetech@gmail.com>
 *
 * 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
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301, USA.
 *
 *
 */

using Gtk;
using Gee;

using TeeJee.Logging;
using TeeJee.FileSystem;
using TeeJee.JsonHelper;
using TeeJee.ProcessHelper;
using TeeJee.GtkHelper;
using TeeJee.System;
using TeeJee.Misc;

class RestoreDeviceBox : Gtk.Box{

	private Gtk.InfoBar infobar_location;
	private Gtk.Label lbl_infobar_location;
	private Gtk.Box option_box;
	private Gtk.Label lbl_header_subvol;
	private bool show_volume_name = false;
	
	private Gtk.SizeGroup sg_mount_point = new Gtk.SizeGroup(SizeGroupMode.HORIZONTAL);
	private Gtk.SizeGroup sg_device = new Gtk.SizeGroup(SizeGroupMode.HORIZONTAL);
	private Gtk.SizeGroup sg_mount_options = new Gtk.SizeGroup(SizeGroupMode.HORIZONTAL);
	private Gtk.Window parent_window;
    private Gtk.IconSize tooltip_size = Gtk.icon_size_register("ttip", 128, 128);

	public RestoreDeviceBox (Gtk.Window _parent_window) {

		log_debug("RestoreDeviceBox: RestoreDeviceBox()");
		
		//base(Gtk.Orientation.VERTICAL, 6); // issue with vala
		GLib.Object(orientation: Gtk.Orientation.VERTICAL, spacing: 6); // work-around
		parent_window = _parent_window;
		margin = 12;

		var hbox = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 6);
		add(hbox);

		add_label_header(hbox, _("Select Target Device"), true);

		// buffer
		var label = add_label(hbox, "");
        label.hexpand = true;
       
		// refresh device button
		
		Gtk.SizeGroup size_group = new Gtk.SizeGroup(SizeGroupMode.HORIZONTAL);
		var btn_refresh = add_button(hbox, _("Refresh"), "", size_group, null);
        btn_refresh.clicked.connect(()=>{
			App.update_partitions();
			refresh();
		});


		if (App.mirror_system){
			add_label(this,
				_("Select the target devices where system will be cloned."));
		}
		else{
			add_label(this,
				_("Select the devices where files will be restored.") + "\n" +
				_("Devices from which snapshot was created are pre-selected."));
		}

		// headings
		
		hbox = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 6);
		hbox.margin_top = 12;
		add(hbox);

		label = add_label(hbox, _("Path") + "  ", true, true);
		label.xalign = (float) 0.0;
		sg_mount_point.add_widget(label);
		
		label = add_label(hbox, _("Device") + "  ", true, true);
		label.xalign = (float) 0.0;
		sg_device.add_widget(label);

		label = add_label(hbox, _("Subvolume"), true, true);
		label.xalign = (float) 0.5;
		label.set_no_show_all(true);
		lbl_header_subvol = label;

		// options
		
		option_box = new Gtk.Box(Gtk.Orientation.VERTICAL, 6);
		add(option_box);

		// bootloader
		
		add_boot_options();

		// infobar
		
		create_infobar_location();

		log_debug("RestoreDeviceBox: RestoreDeviceBox(): exit");
    }

    public void refresh(bool reset_device_selections = true){
		
		log_debug("RestoreDeviceBox: refresh()");
		App.update_partitions();
		create_device_selection_options(reset_device_selections);
		App.init_boot_options();
		log_debug("RestoreDeviceBox: refresh(): exit");
	}

	private void create_device_selection_options(bool reset_device_selections){
		
		if (reset_device_selections){
			App.init_mount_list();
		}

		show_volume_name = false;
		foreach(var entry in App.mount_list){
			if ((entry.device != null) && ((entry.subvolume_name().length > 0) || (entry.lvm_name().length > 0))){
				// subvolumes are used - show the mount options column
				show_volume_name = true;
				break;
			}
		}

		if (show_volume_name){
			lbl_header_subvol.set_no_show_all(false);
		}
		lbl_header_subvol.visible = show_volume_name;

		foreach(var item in option_box.get_children()){
			option_box.remove(item);
		}

		foreach(MountEntry entry in App.mount_list){
			add_device_selection_option(entry);
		}

		show_all();
	}

	private void add_device_selection_option(MountEntry entry){

		var box = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 6);
		option_box.add(box);

		var label = add_label(box, entry.mount_point, true);
		sg_mount_point.add_widget(label);
		
		var combo = add_device_combo(box, entry);
		sg_device.add_widget(combo);

		if (show_volume_name){
			string txt = "";
			if (entry.subvolume_name().length > 0){
				txt = "%s".printf(entry.subvolume_name());
			}
			else {
				txt = "%s".printf(entry.lvm_name());
			}
			label = add_label(box, txt, false);
			sg_mount_options.add_widget(label);
		}
	}

	private Gtk.ComboBox add_device_combo(Gtk.Box box, MountEntry entry){

		var combo = new Gtk.ComboBox();
		box.add(combo);

		var cell_pix = new Gtk.CellRendererPixbuf();
		combo.pack_start (cell_pix, false);

		combo.set_cell_data_func (cell_pix, (cell_layout, cell, model, iter)=>{
			Device dev;
			//bool selected = combo.get_active_iter (out iter);
			//if (!selected) { return; }
			combo.model.get (iter, 0, out dev, -1);

			if (dev != null){

				if (dev.type == "disk"){
					((Gtk.CellRendererPixbuf)cell).icon_name = IconManager.ICON_HARDDRIVE;
				}
			
				((Gtk.CellRendererPixbuf)cell).sensitive = (dev.type != "disk");
				((Gtk.CellRendererPixbuf)cell).visible = (dev.type == "disk");
			}
		});

		var cell_text = new Gtk.CellRendererText();
		cell_text.xalign = (float) 0.0;
		combo.pack_start (cell_text, false);

		combo.has_tooltip = true;
		combo.query_tooltip.connect((x, y, keyboard_tooltip, tooltip) => {
			Device dev;
			TreeIter iter;
			bool selected = combo.get_active_iter (out iter);
			if (!selected) { return true; }
			combo.model.get (iter, 0, out dev, -1);
			
			tooltip.set_icon_from_icon_name(IconManager.ICON_HARDDRIVE, tooltip_size);
			
			if (dev != null){
				tooltip.set_markup(dev.tooltip_text());
			}
			else{
				tooltip.set_markup(_("Keep this mount path on the root filesystem"));
			}
			
			return true;
		});

		combo.set_cell_data_func(cell_text, (cell_layout, cell, model, iter)=>{
			Device dev;
			model.get (iter, 0, out dev, -1);

			if (dev != null){
				((Gtk.CellRendererText)cell).markup = dev.description_simple_formatted();
				((Gtk.CellRendererText)cell).sensitive = (dev.type != "disk");
			}
			else{
				((Gtk.CellRendererText)cell).markup = _("Keep on Root Device");
			}
		});
		
		// populate combo
		var model = new Gtk.ListStore(2, typeof(Device), typeof(MountEntry));
		combo.model = model;
		
		var active = -1;
		var index = -1;
		TreeIter iter;

		if (entry.mount_point != "/"){
			index++;
			model.append(out iter);
			model.set (iter, 0, null);
			model.set (iter, 1, entry);
		}
		
		foreach(var dev in App.partitions){
			// skip disk and loop devices
			//if ((dev.type == "disk")||(dev.type == "loop")){
			//	continue;
			//}

			if ((dev.type == "loop") || (dev.fstype == "iso9660")){
				continue;
			}

			if (dev.type != "disk"){

				// display only linux filesystem for / and /home
				if ((entry.mount_point == "/") || (entry.mount_point == "/home")){
					if (!dev.has_linux_filesystem()){
						continue;
					}
				}

				if (dev.has_children()){
					continue; // skip parent partitions of unlocked volumes (luks)
				}
			}
			
			index++;
			model.append(out iter);
			model.set (iter, 0, dev);
			model.set (iter, 1, entry);
		
			if (entry.device != null){
				if (dev.uuid == entry.device.uuid){
					active = index;
				}
				else if (dev.has_parent() && (dev.parent.uuid == entry.device.uuid)){
					active = index;
				}
				else if (dev.has_children() && (dev.children[0].uuid == entry.device.uuid)){
					// this will not occur since we are skipping parent devices in this loop
					active = index;
				}
			}
		}

		if ((active == -1) && (entry.mount_point != "/")){
			active = 0; // keep on root device
		}

		combo.active = active;
		
		combo.changed.connect((path) => {

			Device current_dev;
			MountEntry current_entry;
			
			TreeIter iter_active;
			bool selected = combo.get_active_iter (out iter_active);
			if (!selected){
				log_debug("device combo: active is -1");
				return;
			}

			TreeIter iter_combo;
			var store = (Gtk.ListStore) combo.model;
			store.get(iter_active, 0, out current_dev, 1, out current_entry, -1);

			if (current_dev.is_encrypted_partition()){

				log_debug("add_device_combo().changed: unlocking encrypted device..");
				
				string msg_out, msg_err;
				var luks_unlocked = Device.luks_unlock(
					current_dev, "", "", parent_window, out msg_out, out msg_err);

				if (luks_unlocked == null){

					log_debug("add_device_combo().changed: failed to unlock");
					
					// reset the selection
					
					if (current_entry.mount_point == "/"){

						// reset to default device
						
						index = -1;
						for (bool next = store.get_iter_first (out iter_combo); next;
							next = store.iter_next (ref iter_combo)) {

							Device dev_iter;
							store.get(iter_combo, 0, out dev_iter, -1);
							index++;
							
							if ((dev_iter != null) && (dev_iter.device == current_entry.device.device)){
								combo.active = index;
							}
						}
					}
					else{
						combo.active = 0; // keep on root device
					}
					
					return;
				}
				else{

					log_debug("add_device_combo().changed: unlocked");
					
					// update current entry
					
					if (current_entry.mount_point == "/"){
						App.dst_root = luks_unlocked;
						App.init_boot_options();
					}

					current_entry.device = luks_unlocked;

					// refresh devices

					refresh(false); // do not reset selections
					return; // no need to continue
				}
			}

			current_entry.device = current_dev;
			
			if (current_entry.mount_point == "/"){
				App.init_boot_options();
			}
		});

		return combo;
	}

	private void add_boot_options(){

		var hbox = new Gtk.ButtonBox(Gtk.Orientation.HORIZONTAL);
		hbox.layout_style = Gtk.ButtonBoxStyle.START;
        add(hbox);
		
		string tt = _("[For Experienced Users] Change these settings if the restored system fails to boot.");
		var button = add_button(hbox, _("Bootloader Options (Advanced)"), tt, null, null);
		button.margin = 10;
		var btn_boot_options = button;

        btn_boot_options.clicked.connect(()=>{
			var win = new BootOptionsWindow();
			win.set_transient_for(parent_window);
		});
	}

	private void create_infobar_location(){
		
		var infobar = new Gtk.InfoBar();
		infobar.no_show_all = true;
		add(infobar);
		infobar_location = infobar;
		
		var content = (Gtk.Box) infobar.get_content_area ();
		var label = add_label(content, "");
		lbl_infobar_location = label;
	}

	public bool check_and_mount_devices(){

		// check if we are restoring the current system
		
		if (App.dst_root == App.sys_root){
			return true; // all required devices are already mounted
		}
		
		// check if target device is selected for /
		
		foreach(var entry in App.mount_list){
			if ((entry.mount_point == "/") && (entry.device == null)){
				
				gtk_messagebox(
					_("Root device not selected"),
					_("Select the device for root file system (/)"),
					parent_window, true);
				
				return false;
			}
		}

		// verify that target device for / is not same as system in clone mode
		
		if (App.mirror_system){

			foreach(var entry in App.mount_list){
				if (entry.mount_point != "/"){ continue; }

				bool same = false;
				if (entry.device.uuid == App.sys_root.uuid){
					same = true;
				}
				else if (entry.device.has_parent() && App.sys_root.has_parent()){
					if (entry.device.uuid == App.sys_root.parent.uuid){
						same = true;
					}
				}
				
				if (same){
					
					gtk_messagebox(
						_("Target device is same as system device"),
						_("Select another device for root file system (/)"),
						parent_window, true);
						
					return false;
				}

				break;
			}
		}

		// check if /boot device is selected for luks partitions
		
		foreach(var entry in App.mount_list){
			if ((entry.mount_point == "/boot") && (entry.device == null)){

				if ((App.dst_root != null) && (App.dst_root.is_on_encrypted_partition())){

					gtk_messagebox(
						_("Boot device not selected"),
						_("An encrypted device is selected for root file system (/). The boot directory (/boot) must be mounted on a non-encrypted device for the system to boot successfully.\n\nEither select a non-encrypted device for boot directory or select a non-encrypted device for root filesystem."),
						parent_window, true);

					return false;
				}
			}
		}

		//check if grub device selected ---------------

		if (App.reinstall_grub2 && (App.grub_device.length == 0)){
			string title =_("GRUB device not selected");
			string msg = _("Please select the GRUB device");
			gtk_messagebox(title, msg, parent_window, true);
			return false;
		}

		// check BTRFS subvolume layout --------------

		bool supported = App.check_btrfs_layout(App.dst_root, App.dst_home, false);
		
		if (!supported){
			var title = _("Unsupported Subvolume Layout")
				+ " (%s)".printf(App.dst_root.device);
			var msg = _("Partition has an unsupported subvolume layout.") + " ";
			msg += _("Only ubuntu-type layouts with @ and @home subvolumes are currently supported.") + "\n\n";
			gtk_messagebox(title, msg, parent_window, true);
			return false;
		}

		// mount target device -------------

		bool status = App.mount_target_devices(parent_window);
		if (status == false){
			string title = _("Error");
			string msg = _("Failed to mount devices");
			gtk_messagebox(title, msg, parent_window, true);
			return false;
		}

		return true;
	}
}