Ddeepin-ci-robotchore: init
781dfa83创建于 2023年9月8日历史提交
/*
 * BackupBox.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 BackupDeviceBox : Gtk.Box{

	private Gtk.TreeView tv_devices;
	private Gtk.InfoBar infobar_location;
	private Gtk.Label lbl_infobar_location;
	private Gtk.Label lbl_common;
	
	private Gtk.Window parent_window;

	public BackupDeviceBox (Gtk.Window _parent_window) {

		log_debug("BackupDeviceBox: BackupDeviceBox()");
		
		//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 Snapshot Location"), true);

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

		// TODO: show this message somewhere
		
		//var msg = _("Only Linux partitions are supported.");
		//msg += "\n" + _("Snapshots will be saved in folder /timeshift");

		// treeview
		init_tv_devices();

		// tooltips
		//tv_devices.set_tooltip_text(msg);

		// infobar
		init_infobar_location();

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

    public void refresh(){
		
		tv_devices_refresh();
		
		check_backup_location();

		if (App.btrfs_mode){
			
			lbl_common.label = "<i>• %s\n• %s\n• %s</i>".printf(
				_("Devices displayed above have BTRFS file systems."),
				_("BTRFS snapshots are saved on system partition. Other partitions are not supported."),
				_("Snapshots are saved to /timeshift-btrfs on selected partition. Other locations are not supported.")
			);
		}
		else {
			lbl_common.label = "<i>• %s\n• %s\n• %s\n• %s</i>".printf(
				_("Devices displayed above have Linux file systems."),
				_("Devices with Windows file systems are not supported (NTFS, FAT, etc)."),
				_("Remote and network locations are not supported."),
				_("Snapshots are saved to /timeshift on selected partition. Other locations are not supported.")
			);
		}
	}

	private void init_tv_devices(){
		
		tv_devices = add_treeview(this);
		tv_devices.vexpand = true;
		tv_devices.headers_clickable = true;
		//tv_devices.rules_hint = true;
		tv_devices.activate_on_single_click = true;
		//tv_devices.headers_clickable  = true;
		
		// device name
		
		Gtk.CellRendererPixbuf cell_pix;
		Gtk.CellRendererToggle cell_radio;
		Gtk.CellRendererText cell_text;
		//var col = add_column_radio_and_text(tv_devices, _("Disk"), out cell_radio, out cell_text);
		var col = add_column_icon_radio_text(tv_devices, _("Disk"),
			out cell_pix, out cell_radio, out cell_text);

		col.resizable = true;
		
		col.set_cell_data_func(cell_pix, (cell_layout, cell, model, iter)=>{
			Device dev;
			model.get (iter, 0, out dev, -1);

			((Gtk.CellRendererPixbuf)cell).visible = (dev.type == "disk");
			
		});

        col.add_attribute(cell_pix, "icon-name", 2);

		col.set_cell_data_func(cell_radio, (cell_layout, cell, model, iter)=>{
			Device dev;
			bool selected;
			model.get (iter, 0, out dev, 3, out selected, -1);

			((Gtk.CellRendererToggle)cell).active = selected;

			((Gtk.CellRendererToggle)cell).visible =
				(dev.size_bytes > 10 * KB) && (dev.type != "disk") && (dev.children.size == 0);
		});

		//cell_radio.toggled.connect((path)=>{});

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

			/*if (dev.type == "disk"){
				var txt = "%s %s".printf(dev.model, dev.vendor).strip();
				if (txt.length == 0){
					txt = "%s Disk".printf(format_file_size(dev.size_bytes));
				}
				else{
					txt += " (%s Disk)".printf(format_file_size(dev.size_bytes));
				}
				(cell as Gtk.CellRendererText).text = txt.strip();
			}
			else {
				(cell as Gtk.CellRendererText).text = dev.description_full_free();
			}*/

			if (dev.type == "disk"){
				var txt = "%s %s".printf(dev.model, dev.vendor).strip();
				if (txt.length == 0){
					txt = "%s Disk".printf(format_file_size(dev.size_bytes));
				}
				((Gtk.CellRendererText)cell).text = txt.strip();
			}
			else {
				((Gtk.CellRendererText)cell).text = dev.kname;
			}

			//(cell as Gtk.CellRendererText).sensitive = (dev.type != "disk");
		});

		
		// type
		
		col = add_column_text(tv_devices, _("Type"), out cell_text);

		col.set_cell_data_func(cell_text, (cell_layout, cell, model, iter)=>{
			Device dev;
			model.get (iter, 0, out dev, -1);
			((Gtk.CellRendererText)cell).text = dev.fstype;

			//(cell as Gtk.CellRendererText).sensitive = (dev.type != "disk");
		});

		// size
		
		col = add_column_text(tv_devices, _("Size"), out cell_text);
		cell_text.xalign = (float) 1.0;
		
		col.set_cell_data_func(cell_text, (cell_layout, cell, model, iter)=>{
			Device dev;
			model.get (iter, 0, out dev, -1);

			((Gtk.CellRendererText)cell).text =
					(dev.size_bytes > 0) ? format_file_size(dev.size_bytes, false, "", true, 0) : "";
		});

		// free
		
		col = add_column_text(tv_devices, _("Free"), out cell_text);
		cell_text.xalign = (float) 1.0;
		
		col.set_cell_data_func(cell_text, (cell_layout, cell, model, iter)=>{
			Device dev;
			model.get (iter, 0, out dev, -1);

			if (dev.type == "disk"){
				((Gtk.CellRendererText)cell).text = "";
			}
			else{
				((Gtk.CellRendererText)cell).text =
					(dev.free_bytes > 0) ? format_file_size(dev.free_bytes, false, "", true, 0) : "";
			}

			((Gtk.CellRendererText)cell).sensitive = (dev.type != "disk");
		});

		// name
		
		col = add_column_text(tv_devices, _("Name"), out cell_text);
		cell_text.xalign = 0.0f;
		
		col.set_cell_data_func(cell_text, (cell_layout, cell, model, iter)=>{
			Device dev;
			model.get (iter, 0, out dev, -1);

			if (dev.type == "disk"){
				((Gtk.CellRendererText)cell).text = "";
			}
			else{
				((Gtk.CellRendererText)cell).text = dev.partlabel;
			}

			((Gtk.CellRendererText)cell).sensitive = (dev.type != "disk");
		});

		// label
		
		col = add_column_text(tv_devices, _("Label"), out cell_text);
		cell_text.xalign = 0.0f;
		
		col.set_cell_data_func(cell_text, (cell_layout, cell, model, iter)=>{
			Device dev;
			model.get (iter, 0, out dev, -1);

			if (dev.type == "disk"){
				((Gtk.CellRendererText)cell).text = "";
			}
			else{
				((Gtk.CellRendererText)cell).text = dev.label;
			}

			((Gtk.CellRendererText)cell).sensitive = (dev.type != "disk");
		});
		
		// buffer

		col = add_column_text(tv_devices, "", out cell_text);
		col.expand = true;
		
		/*// label
		
		col = add_column_text(tv_devices, _("Label"), out cell_text);

		col.set_cell_data_func(cell_text, (cell_layout, cell, model, iter)=>{
			Device dev;
			model.get (iter, 0, out dev, -1);
			(cell as Gtk.CellRendererText).text = dev.label;

			(cell as Gtk.CellRendererText).sensitive = (dev.type != "disk");
		});*/

		
		
		// events

		tv_devices.row_activated.connect((path, column) => {
			var store = (Gtk.TreeStore) tv_devices.model;
			var selection = tv_devices.get_selection();

			selection.selected_foreach((model, path, iter) => {
				Device dev;
				store.get (iter, 0, out dev);

				if ((App.repo.device == null) || (App.repo.device.uuid != dev.uuid)){
					try_change_device(dev);
				}
				else{
					return;
				}
			});

			store.foreach((model, path, iter) => {
				Device dev;
				store.get (iter, 0, out dev);
				
				if ((App.repo.device != null) && (App.repo.device.uuid == dev.uuid)){
					store.set (iter, 3, true);
					//tv_devices.get_selection().select_iter(iter);
				}
				else{
					store.set (iter, 3, false);
				}

				return false; // continue
			});
		});
	}

	private void init_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;

		// scrolled
		var scrolled = new Gtk.ScrolledWindow(null, null);
		scrolled.set_shadow_type (ShadowType.ETCHED_IN);
		scrolled.hscrollbar_policy = Gtk.PolicyType.NEVER;
		scrolled.vscrollbar_policy = Gtk.PolicyType.NEVER;
		scrolled.set_size_request(-1, 100);
		this.add(scrolled);
		
		label = new Gtk.Label("");
		label.set_use_markup(true);
		label.xalign = (float) 0.0;
		label.wrap = true;
		label.wrap_mode = Pango.WrapMode.WORD;
		label.margin = 6;
		scrolled.add(label);
		lbl_common = label;
	}

	private void try_change_device(Device dev){

		log_debug("try_change_device: %s".printf(dev.device));
		
		if (dev.type == "disk"){

			bool found_child = false;

			if ((App.btrfs_mode && (dev.fstype == "btrfs")) || (!App.btrfs_mode && dev.has_linux_filesystem())){
				
				change_backup_device(dev);
				found_child = true;
			}

			if (!found_child){

				// find first valid partition
				
				foreach (var child in dev.children){
					
					if ((App.btrfs_mode && (child.fstype == "btrfs")) || (!App.btrfs_mode && child.has_linux_filesystem())){
						
						change_backup_device(child);
						found_child = true;
						break;
					}
				}
			}
			
			if (!found_child){
				
				string msg = _("Selected device does not have Linux partition");
				
				if (App.btrfs_mode){
					msg = _("Selected device does not have BTRFS partition");
				}
				
				lbl_infobar_location.label = "<span weight=\"bold\">%s</span>".printf(msg);
				infobar_location.message_type = Gtk.MessageType.ERROR;
				infobar_location.no_show_all = false;
				infobar_location.show_all();
			}
		}
		else if (dev.has_children()){
			
			// select the child instead of parent
			change_backup_device(dev.children[0]);
		}
		else if (!dev.has_children()){
			
			// select the device
			change_backup_device(dev);
		}
		else {
			
			// ask user to select
			lbl_infobar_location.label = "<span weight=\"bold\">%s</span>".printf(_("Select a partition on this disk"));
			infobar_location.message_type = Gtk.MessageType.ERROR;
			infobar_location.no_show_all = false;
			infobar_location.show_all();
		}
	}

	private void change_backup_device(Device pi){
		
		// return if device has not changed
		if ((App.repo.device != null) && (pi.uuid == App.repo.device.uuid)){ return; }

		gtk_set_busy(true, parent_window);

		log_debug("\n");
		log_msg("selected device: %s".printf(pi.device));
		log_debug("fstype: %s".printf(pi.fstype));

		App.repo = new SnapshotRepo.from_device(pi, parent_window, App.btrfs_mode);

		if (pi.fstype == "luks"){
			
			App.update_partitions();

			var dev = Device.find_device_in_list(App.partitions, pi.uuid);
			
			if (dev.has_children()){
				
				log_debug("has children");
				
				if (dev.children[0].has_linux_filesystem()){
					
					log_debug("has linux filesystem: %s".printf(dev.children[0].fstype));
					log_msg("selecting child device: %s".printf(dev.children[0].device));
						
					App.repo = new SnapshotRepo.from_device(dev.children[0], parent_window, App.btrfs_mode);
					tv_devices_refresh();
				}
				else{
					log_debug("does not have linux filesystem");
				}
			}
		}

		check_backup_location();

		gtk_set_busy(false, parent_window);
	}

	private bool check_backup_location(){
		
		bool ok = true;

		App.repo.check_status();
		string message = App.repo.status_message;
		string details = App.repo.status_details;
		int status_code = App.repo.status_code;
		
		// TODO: call check on repo directly
		
		message = escape_html(message);
		details = escape_html(details);
		
		if (App.live_system()){
			
			switch (status_code){
			case SnapshotLocationStatus.NOT_SELECTED:
				lbl_infobar_location.label = "<span weight=\"bold\">%s</span>".printf(details);
				infobar_location.message_type = Gtk.MessageType.ERROR;
				infobar_location.no_show_all = false;
				infobar_location.show_all();
				ok = false;
				break;
				
			case SnapshotLocationStatus.NOT_AVAILABLE:
				lbl_infobar_location.label = "<span weight=\"bold\">%s</span>".printf(message);
				infobar_location.message_type = Gtk.MessageType.ERROR;
				infobar_location.no_show_all = false;
				infobar_location.show_all();
				ok = false;
				break;

			case SnapshotLocationStatus.READ_ONLY_FS:
			case SnapshotLocationStatus.HARDLINKS_NOT_SUPPORTED:
				lbl_infobar_location.label = "<span weight=\"bold\">%s</span>".printf(message);
				infobar_location.message_type = Gtk.MessageType.ERROR;
				infobar_location.no_show_all = false;
				infobar_location.show_all();
				ok = false;
				break;

			case SnapshotLocationStatus.NO_BTRFS_SYSTEM:
				lbl_infobar_location.label = "<span weight=\"bold\">%s</span>".printf(details);
				infobar_location.message_type = Gtk.MessageType.ERROR;
				infobar_location.no_show_all = false;
				infobar_location.show_all();
				ok = false;
				break;

			case SnapshotLocationStatus.NO_SNAPSHOTS_HAS_SPACE:
			case SnapshotLocationStatus.NO_SNAPSHOTS_NO_SPACE:
				lbl_infobar_location.label = "<span weight=\"bold\">%s</span>".printf(
					_("There are no snapshots on this device"));
				infobar_location.message_type = Gtk.MessageType.ERROR;
				infobar_location.no_show_all = false;
				infobar_location.show_all();
				//ok = false;
				break;

			case SnapshotLocationStatus.HAS_SNAPSHOTS_NO_SPACE:
			case SnapshotLocationStatus.HAS_SNAPSHOTS_HAS_SPACE:
				infobar_location.hide();
				break;
			}
		}
		else{
			switch (status_code){
				case SnapshotLocationStatus.NOT_SELECTED:
					lbl_infobar_location.label = "<span weight=\"bold\">%s</span>".printf(details);
					infobar_location.message_type = Gtk.MessageType.ERROR;
					infobar_location.no_show_all = false;
					infobar_location.show_all();
					ok = false;
					break;
					
				case SnapshotLocationStatus.NOT_AVAILABLE:
				case SnapshotLocationStatus.HAS_SNAPSHOTS_NO_SPACE:
				case SnapshotLocationStatus.NO_SNAPSHOTS_NO_SPACE:
					lbl_infobar_location.label = "<span weight=\"bold\">%s</span>".printf(
						message.replace("<","&lt;"));
					infobar_location.message_type = Gtk.MessageType.ERROR;
					infobar_location.no_show_all = false;
					infobar_location.show_all();
					ok = false;
					break;

				case SnapshotLocationStatus.READ_ONLY_FS:
				case SnapshotLocationStatus.HARDLINKS_NOT_SUPPORTED:
					lbl_infobar_location.label = "<span weight=\"bold\">%s</span>".printf(message);
					infobar_location.message_type = Gtk.MessageType.ERROR;
					infobar_location.no_show_all = false;
					infobar_location.show_all();
					ok = false;
					break;

				case SnapshotLocationStatus.NO_BTRFS_SYSTEM:
					lbl_infobar_location.label = "<span weight=\"bold\">%s</span>".printf(details);
					infobar_location.message_type = Gtk.MessageType.ERROR;
					infobar_location.no_show_all = false;
					infobar_location.show_all();
					ok = false;
					break;

				case 3:
				case 0:
					infobar_location.hide();
					// TODO: Show a disk icon with stats when selected device is OK
					break;
			}

		}
		
		return ok;
	}

	private void tv_devices_refresh(){
		
		App.update_partitions();

		var model = new Gtk.TreeStore(4,
			typeof(Device),
			typeof(string),
			typeof(string),
			typeof(bool));
		
		tv_devices.set_model (model);

		TreeIter iter0;

		foreach(var disk in App.partitions) {
			
			if (disk.type != "disk") { continue; }

			model.append(out iter0, null);
			model.set(iter0, 0, disk, -1);
			model.set(iter0, 1, disk.tooltip_text(), -1);
			model.set(iter0, 2, IconManager.ICON_HARDDRIVE, -1);
			model.set(iter0, 3, false, -1);

			tv_append_child_volumes(ref model, ref iter0, disk);
		}

		tv_devices.expand_all();
		tv_devices.columns_autosize();
	}

	private void tv_append_child_volumes(
		ref Gtk.TreeStore model, ref Gtk.TreeIter iter0, Device parent){

		foreach(var part in App.partitions) {

			if (!part.has_linux_filesystem()){ continue; }

			if (App.btrfs_mode){
				if (part.is_encrypted_partition() && (!part.has_children() || (part.children[0].fstype == "btrfs"))){
					//ok
				}
				else if (part.is_lvm_partition() && (!part.has_children() || (part.children[0].fstype == "btrfs"))){
					//ok
				}
				else if (part.fstype == "btrfs"){
					//ok
				}
				else{
					continue;
				}
			}
			
			if (part.pkname == parent.kname) {
				
				TreeIter iter1;
				model.append(out iter1, iter0);
				model.set(iter1, 0, part, -1);
				model.set(iter1, 1, part.tooltip_text(), -1);
				model.set(iter1, 2, (part.fstype == "luks") ? "locked" : IconManager.ICON_HARDDRIVE, -1);
				
				if (parent.fstype == "luks"){
					// change parent's icon to unlocked
					model.set(iter0, 2, "unlocked", -1);
				}

				if ((App.repo.device != null) && (part.uuid == App.repo.device.uuid)){
					model.set(iter1, 3, true, -1);
				}
				else{
					model.set(iter1, 3, false, -1);
				}

				tv_append_child_volumes(ref model, ref iter1, part);
			}
			else if ((part.kname == parent.kname) && (part.type == "disk")
				&& part.has_linux_filesystem() && !part.has_children()){
				
				// partition-less disk with linux filesystem

				// create a dummy partition
				var part2 = new Device();
				part2.copy_fields_from(part);
				part2.type = "part";
				part2.pkname = part.device.replace("/dev/","");
				part2.parent = part;

				TreeIter iter1;
				model.append(out iter1, iter0);
				model.set(iter1, 0, part2, -1);
				model.set(iter1, 1, part2.tooltip_text(), -1);
				model.set(iter1, 2, (part2.fstype == "luks") ? "locked" : IconManager.ICON_HARDDRIVE, -1);
				
				if ((App.repo.device != null) && (part2.uuid == App.repo.device.uuid)){
					model.set(iter1, 3, true, -1);
				}
				else{
					model.set(iter1, 3, false, -1);
				}
			}
		}
	}
}