Ddeepin-ci-robotchore: init
781dfa83创建于 2023年9月8日历史提交
/*
 * SnapshotListBox.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 SnapshotListBox : Gtk.Box{
	
	public Gtk.TreeView treeview;
    private Gtk.TreeViewColumn col_date;
    private Gtk.TreeViewColumn col_tags;
    private Gtk.TreeViewColumn col_size;
    private Gtk.TreeViewColumn col_unshared;
    private Gtk.TreeViewColumn col_system;
    private Gtk.TreeViewColumn col_desc;
	private int treeview_sort_column_index = 0;
	private bool treeview_sort_column_desc = true;

	private Gtk.Menu menu_snapshots;
	private Gtk.ImageMenuItem mi_browse;
	private Gtk.ImageMenuItem mi_remove;
	private Gtk.ImageMenuItem mi_mark;
	private Gtk.ImageMenuItem mi_view_log_create;
	private Gtk.ImageMenuItem mi_view_log_restore;
	
	private Gtk.Window parent_window;

	public signal void delete_selected();
	public signal void mark_selected();
	public signal void browse_selected();
	public signal void view_snapshot_log(bool show_restore_log);

	public SnapshotListBox (Gtk.Window _parent_window) {

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

		init_treeview();
		
		init_list_view_context_menu();

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

    private void init_treeview(){
		
		//treeview
		treeview = new TreeView();
		treeview.get_selection().mode = SelectionMode.MULTIPLE;
		treeview.headers_clickable = true;
		treeview.has_tooltip = true;

		//sw_backups
		var sw_backups = new ScrolledWindow(null, null);
		sw_backups.set_shadow_type (ShadowType.ETCHED_IN);
		sw_backups.add (treeview);
		sw_backups.expand = true;
		add(sw_backups);

        //col_date
		col_date = new TreeViewColumn();
		col_date.title = _("Snapshot");
		col_date.clickable = true;
		col_date.resizable = true;
		col_date.spacing = 1;
		col_date.min_width = 200;

		var cell_backup_icon = new CellRendererPixbuf ();
		cell_backup_icon.surface = IconManager.lookup_surface("clock", 16, treeview.scale_factor);
		//cell_backup_icon.xpad = 1;
		cell_backup_icon.xpad = 4;
		cell_backup_icon.ypad = 6;
		col_date.pack_start (cell_backup_icon, false);

		var cell_date = new CellRendererText ();
		col_date.pack_start (cell_date, false);
		col_date.set_cell_data_func (cell_date, cell_date_render);

		treeview.append_column(col_date);

		col_date.clicked.connect(() => {
			if(treeview_sort_column_index == 0){
				treeview_sort_column_desc = !treeview_sort_column_desc;
			}
			else{
				treeview_sort_column_index = 0;
				treeview_sort_column_desc = true;
			}
			refresh();
		});

		//col_system
		col_system = new TreeViewColumn();
		col_system.title = _("System");
		col_system.resizable = true;
		col_system.clickable = true;
		col_system.min_width = 200;

		var cell_system = new CellRendererText ();
		cell_system.ellipsize = Pango.EllipsizeMode.END;
		col_system.pack_start (cell_system, false);
		col_system.set_cell_data_func (cell_system, cell_system_render);
		treeview.append_column(col_system);

		col_system.clicked.connect(() => {
			if(treeview_sort_column_index == 1){
				treeview_sort_column_desc = !treeview_sort_column_desc;
			}
			else{
				treeview_sort_column_index = 1;
				treeview_sort_column_desc = false;
			}
			refresh();
		});

		//col_tags
		col_tags = new TreeViewColumn();
		col_tags.title = _("Tags");
		col_tags.resizable = true;
		//col_tags.min_width = 80;
		col_tags.clickable = true;
		var cell_tags = new CellRendererText ();
		cell_tags.ellipsize = Pango.EllipsizeMode.END;
		col_tags.pack_start (cell_tags, false);
		col_tags.set_cell_data_func (cell_tags, cell_tags_render);
		treeview.append_column(col_tags);

		col_tags.clicked.connect(() => {
			if(treeview_sort_column_index == 2){
				treeview_sort_column_desc = !treeview_sort_column_desc;
			}
			else{
				treeview_sort_column_index = 2;
				treeview_sort_column_desc = false;
			}
			refresh();
		});

		//col_size
		var col = new TreeViewColumn();
		col.title = _("Size");
		col.resizable = true;
		col.min_width = 80;
		col.clickable = true;
		var cell_size = new CellRendererText ();
		cell_size.ellipsize = Pango.EllipsizeMode.END;
		cell_size.xalign = (float) 1.0;
		col.pack_start (cell_size, false);
		col.set_cell_data_func (cell_size, cell_size_render);
		col_size = col;
		treeview.append_column(col_size);

		col_size.clicked.connect(() => {
			if(treeview_sort_column_index == 2){
				treeview_sort_column_desc = !treeview_sort_column_desc;
			}
			else{
				treeview_sort_column_index = 2;
				treeview_sort_column_desc = false;
			}
			refresh();
		});

		//col_unshared
		col = new TreeViewColumn();
		col.title = _("Unshared");
		col.resizable = true;
		col.min_width = 80;
		col.clickable = true;
		var cell_unshared = new CellRendererText ();
		cell_unshared.ellipsize = Pango.EllipsizeMode.END;
		cell_unshared.xalign = (float) 1.0;
		col.pack_start (cell_unshared, false);
		col.set_cell_data_func (cell_unshared, cell_unshared_render);
		col_unshared = col;
		treeview.append_column(col_unshared);

		col_unshared.clicked.connect(() => {
			if(treeview_sort_column_index == 2){
				treeview_sort_column_desc = !treeview_sort_column_desc;
			}
			else{
				treeview_sort_column_index = 2;
				treeview_sort_column_desc = false;
			}
			refresh();
		});

		//cell_desc
		col_desc = new TreeViewColumn();
		col_desc.title = _("Comments (click to edit)");
		col_desc.resizable = true;
		col_desc.clickable = true;
		col_desc.expand = true;
		var cell_desc = new CellRendererText ();
		cell_desc.ellipsize = Pango.EllipsizeMode.END;
		col_desc.pack_start (cell_desc, false);
		col_desc.set_cell_data_func (cell_desc, cell_desc_render);
		treeview.append_column(col_desc);
		
		cell_desc.editable = true;
		cell_desc.edited.connect ((path, new_text)=>{
			Snapshot bak;
			TreeIter iter;
			var model = (Gtk.ListStore) treeview.model;
			model.get_iter_from_string (out iter, path);
			model.get (iter, 0, out bak, -1);
			bak.description = new_text;
			bak.update_control_file();
		});

		var col_buffer = new TreeViewColumn();
		var cell_text = new CellRendererText();
		cell_text.width = 20;
		col_buffer.pack_start (cell_text, false);
		treeview.append_column(col_buffer);
		
		//tooltips
		treeview.query_tooltip.connect ((x, y, keyboard_tooltip, tooltip) => {
			
			TreeModel model;
			TreePath path;
			TreeIter iter;
			TreeViewColumn column;
			
			if (treeview.get_tooltip_context (ref x, ref y, keyboard_tooltip, out model, out path, out iter)){
				
				int bx, by;
				treeview.convert_widget_to_bin_window_coords(x, y, out bx, out by);
				
				if (treeview.get_path_at_pos (bx, by, null, out column, null, null)){
					
					if ((column == col_date) || (column == col_system)){

						Snapshot bak;
						model.get (iter, 0, out bak, -1);

						string txt = "";

						if (App.btrfs_mode){

							txt += "<b>%s: %d</b>\n".printf(_("Subvolumes"), bak.subvolumes.values.size);
							
							foreach(var subvol in bak.subvolumes_sorted){
								if (txt.length > 0) { txt += "\n"; }
								txt += "%s".printf(subvol.path);
							}
						}
						else{
							txt = bak.path;
						}
						
						tooltip.set_markup(txt);
						return true;
					}
					else if (column == col_desc){
						tooltip.set_markup(_("<b>Comments</b> (double-click to edit)"));
						return true;
					}
					else if (column == col_tags){
						tooltip.set_markup(
							"<b>%s</b>\n\nO \t%s\nB \t%s\nH \t%s\nD \t%s\nW \t%s\nM \t%s".printf(
								_("Snapshot Levels"),
								_("On demand (manual)"),
								_("Boot"),
								_("Hourly"),
								_("Daily"),
								_("Weekly"),
								_("Monthly"))
						);
						return true;
					}
				}
			}

			return false;
		});
	}

	private void init_list_view_context_menu(){
		
		Gdk.RGBA gray = Gdk.RGBA();
		gray.parse ("rgba(200,200,200,1)");

		// menu_file
		menu_snapshots = new Gtk.Menu();

		// mi_remove
		var item = new ImageMenuItem.with_label(_("Delete"));
		item.image = IconManager.lookup_image("edit-delete", 16);
		item.activate.connect(()=> { delete_selected(); });
		menu_snapshots.append(item);
		mi_remove = item;
		
		// mi_mark
		item = new ImageMenuItem.with_label(_("Mark/Unmark for Deletion"));
		item.image = IconManager.lookup_image("edit-delete", 16);
		item.activate.connect(()=> { mark_selected(); });
		menu_snapshots.append(item);
		mi_mark = item;
		
		// mi_browse
		item = new ImageMenuItem.with_label(_("Browse Files"));
        item.image = IconManager.lookup_image(IconManager.GENERIC_ICON_DIRECTORY, 16);
		item.activate.connect(()=> { browse_selected(); });
		menu_snapshots.append(item);
		mi_browse = item;
		
		// mi_view_log_create
		item = new ImageMenuItem.with_label(_("View Rsync Log for Create"));
        item.image = IconManager.lookup_image(IconManager.GENERIC_ICON_FILE, 16);
		item.activate.connect(()=> { view_snapshot_log(false); });
		menu_snapshots.append(item);
		mi_view_log_create = item;
		
		// mi_view_log_restore
		item = new ImageMenuItem.with_label(_("View Rsync Log for Restore"));
        item.image = IconManager.lookup_image(IconManager.GENERIC_ICON_FILE, 16);
		item.activate.connect(()=> { view_snapshot_log(true); });
		menu_snapshots.append(item);
		mi_view_log_restore = item;
		
		menu_snapshots.show_all();

		// connect signal for shift+F10
        treeview.popup_menu.connect(treeview_popup_menu);
        
        // connect signal for right-click
		treeview.button_press_event.connect(treeview_button_press_event);
	}

	// signals
	
	private bool treeview_popup_menu(){
		
		return menu_snapshots_popup (menu_snapshots, null);
	}

	private bool treeview_button_press_event(Gdk.EventButton event){
		
		if (event.button == 3) {
			return menu_snapshots_popup (menu_snapshots, event);
		}

		return false;
	}
	
	// renderers
	
    private void cell_date_render(
		CellLayout cell_layout, CellRenderer cell, TreeModel model, TreeIter iter){
			
		Snapshot bak;
		model.get (iter, 0, out bak, -1);
		
		var ctxt = (cell as Gtk.CellRendererText);
		ctxt.text = bak.date_formatted;
		ctxt.sensitive = !bak.marked_for_deletion;

		if (bak.live){
			ctxt.markup = "<b>%s</b>".printf(ctxt.text);
		}
		else{
			ctxt.markup = ctxt.text;
		}
		
		// Note: Avoid AM/PM as it may be hidden due to locale settings
	}

	private void cell_tags_render(
		CellLayout cell_layout, CellRenderer cell, TreeModel model, TreeIter iter){
			
		Snapshot bak;
		model.get (iter, 0, out bak, -1);
		
		var ctxt = (cell as Gtk.CellRendererText);
		ctxt.text = bak.taglist_short;
		ctxt.sensitive = !bak.marked_for_deletion;

		if (bak.live){
			ctxt.markup = "<b>%s</b>".printf(ctxt.text);
		}
		else{
			ctxt.markup = ctxt.text;
		}
	}

	private void cell_size_render(
		CellLayout cell_layout, CellRenderer cell, TreeModel model, TreeIter iter){
			
		Snapshot bak;
		model.get (iter, 0, out bak, -1);
		
		var ctxt = (cell as Gtk.CellRendererText);

		if (bak.btrfs_mode){
			
			int64 size = 0;
			
			if (bak.subvolumes.has_key("@")){
				size += bak.subvolumes["@"].total_bytes;
			}
			
			if (bak.subvolumes.has_key("@home")){
				size += bak.subvolumes["@home"].total_bytes;
			}
			
			ctxt.text = format_file_size(size);
		}
		else{
			ctxt.text = "";
		}
		
		ctxt.sensitive = !bak.marked_for_deletion;

		if (bak.live){
			ctxt.markup = "<b>%s</b>".printf(ctxt.text);
		}
		else{
			ctxt.markup = ctxt.text;
		}
	}

	private void cell_unshared_render(
		CellLayout cell_layout, CellRenderer cell, TreeModel model, TreeIter iter){
			
		Snapshot bak;
		model.get (iter, 0, out bak, -1);
		
		var ctxt = (cell as Gtk.CellRendererText);

		if (bak.btrfs_mode){
			
			int64 size = 0;
			
			if (bak.subvolumes.has_key("@")){
				size += bak.subvolumes["@"].unshared_bytes;
			}
			if (bak.subvolumes.has_key("@home")){
				size += bak.subvolumes["@home"].unshared_bytes;
			}
			
			ctxt.text = format_file_size(size);
		}
		else{
			ctxt.text = "";
		}
		
		ctxt.sensitive = !bak.marked_for_deletion;

		if (bak.live){
			ctxt.markup = "<b>%s</b>".printf(ctxt.text);
		}
		else{
			ctxt.markup = ctxt.text;
		}
	}

	private void cell_system_render(
		CellLayout cell_layout, CellRenderer cell, TreeModel model, TreeIter iter){
			
		Snapshot bak;
		model.get (iter, 0, out bak, -1);
		
		var ctxt = (cell as Gtk.CellRendererText);
		ctxt.text = bak.sys_distro;

		if ("LinuxMint" in ctxt.text) {
			ctxt.text = ctxt.text.replace("LinuxMint", "Linux Mint");
		}

		ctxt.sensitive = !bak.marked_for_deletion;

		if (bak.live){
			ctxt.markup = "<b>%s</b>".printf(ctxt.text);
		}
		else{
			ctxt.markup = ctxt.text;
		}
	}

	private void cell_desc_render(
		CellLayout cell_layout, CellRenderer cell, TreeModel model, TreeIter iter){
		Snapshot bak;
		model.get (iter, 0, out bak, -1);

		var ctxt = (cell as Gtk.CellRendererText);
		ctxt.text = bak.description;
		ctxt.sensitive = !bak.marked_for_deletion;
		if (bak.live){
			ctxt.text = "[" + _("LIVE") + "] " + ctxt.text;
		}

		if (bak.live){
			ctxt.markup = "<b>%s</b>".printf(ctxt.text);
		}
		else{
			ctxt.markup = ctxt.text;
		}
	}

	private bool menu_snapshots_popup (Gtk.Menu popup, Gdk.EventButton? event) {
		
		var selected = selected_snapshots();
		
		mi_remove.sensitive = (selected.size > 0);
		mi_mark.sensitive = (selected.size > 0);
		mi_view_log_create.sensitive = !App.btrfs_mode;
		mi_view_log_restore.sensitive = !App.btrfs_mode;

		if (!App.btrfs_mode){

			if (selected.size > 0){
				
				mi_view_log_restore.sensitive = file_exists(selected[0].rsync_restore_log_file)
					|| file_exists(selected[0].rsync_restore_changes_log_file);
			}
		}

		if (event != null) {
			menu_snapshots.popup (null, null, null, event.button, event.time);
		} else {
			menu_snapshots.popup (null, null, null, 0, Gtk.get_current_event_time());
		}
		
		return true;
	}

	// actions
	
	public void refresh(){

		var model = new Gtk.ListStore(1, typeof(Snapshot));

		if ((App.repo == null) || !App.repo.available()){
			treeview.set_model (model);
			return;
		}

		App.repo.load_snapshots();
		
		var list = App.repo.snapshots;

		if (treeview_sort_column_index == 0){

			if (treeview_sort_column_desc)
			{
				list.sort((a,b) => {
					Snapshot t1 = (Snapshot) a;
					Snapshot t2 = (Snapshot) b;

					return (t1.date.compare(t2.date));
				});
			}
			else{
				list.sort((a,b) => {
					Snapshot t1 = (Snapshot) a;
					Snapshot t2 = (Snapshot) b;

					return -1 * (t1.date.compare(t2.date));
				});
			}
		}
		else{
			if (treeview_sort_column_desc)
			{
				list.sort((a,b) => {
					Snapshot t1 = (Snapshot) a;
					Snapshot t2 = (Snapshot) b;

					return strcmp(t1.taglist,t2.taglist);
				});
			}
			else{
				list.sort((a,b) => {
					Snapshot t1 = (Snapshot) a;
					Snapshot t2 = (Snapshot) b;

					return -1 * strcmp(t1.taglist,t2.taglist);
				});
			}
		}

		TreeIter iter;
		foreach(Snapshot bak in list) {
			model.append(out iter);
			model.set (iter, 0, bak);
		}

		col_size.visible = App.btrfs_qgroups_enabled;
		col_unshared.visible = App.btrfs_qgroups_enabled;

		treeview.set_model (model);
		treeview.columns_autosize ();
	}

	public void hide_context_menu(){
		
		// disconnect signal for shift+F10
        treeview.popup_menu.disconnect(treeview_popup_menu);
        
        // disconnect signal for right-click
		treeview.button_press_event.disconnect(treeview_button_press_event);
	}

	public Gee.ArrayList<Snapshot> selected_snapshots(){
		
		var list = new Gee.ArrayList<Snapshot>();

		TreeIter iter;
		var store = (Gtk.ListStore) treeview.model;
		var sel = treeview.get_selection();
		bool iterExists = store.get_iter_first (out iter);
		while (iterExists) {
			if (sel.iter_is_selected (iter)){
				Snapshot bak;
				store.get (iter, 0, out bak);

				list.add(bak);
			}
			iterExists = store.iter_next (ref iter);
		}

		return list;
	}
}