// Use libraries use adw::prelude::*; use adw::*; use glib::*; /// Use all gtk4 libraries (gtk4 -> gtk because cargo) /// Use all libadwaita libraries (libadwaita -> adw because cargo) use gtk::*; use std::thread; use std::cell::{RefCell}; use std::rc::Rc; use duct::cmd; use std::{ collections::HashSet, hash::Hash, io::{BufRead, BufReader}, process::{Command}, time::{Duration}, }; use crate::drive_mount_row::DriveMountRow; use serde::*; #[derive(PartialEq, Debug, Eq, Hash, Clone, Serialize, Deserialize)] pub struct DriveMount { pub partition: String, pub mountpoint: String, pub mountopt: String, } fn create_mount_row( listbox: >k::ListBox, manual_drive_mount_array: &Rc>>, part_table_array: &Rc>>, _check_part_unique: &Rc>, ) -> DriveMountRow { let partition_scroll_child = gtk::ListBox::builder().build(); let partitions_scroll = gtk::ScrolledWindow::builder() .hexpand(true) .vexpand(true) .child(&partition_scroll_child) .build(); // Create row let row = DriveMountRow::new_with_scroll(&partitions_scroll); let null_checkbutton = gtk::CheckButton::builder().build(); let part_table_array_ref = part_table_array.borrow_mut(); for partition in part_table_array_ref.iter() { let partition_size_cli = Command::new("sudo") .arg("/usr/lib/pika/pika-installer-gtk4/scripts/partition-utility.sh") .arg("get_part_size") .arg(partition.clone()) .output() .expect("failed to execute process"); let partition_fs_cli = Command::new("sudo") .arg("/usr/lib/pika/pika-installer-gtk4/scripts/partition-utility.sh") .arg("get_part_fs") .arg(partition.clone().replace("mapper/", "")) .output() .expect("failed to execute process"); let partition_size = String::from_utf8(partition_size_cli.stdout) .expect("Failed to create float") .trim() .parse::() .unwrap(); let partition_fs = String::from_utf8(partition_fs_cli.stdout).expect("Failed read stdout"); let partition_button = gtk::CheckButton::builder() .valign(Align::Center) .can_focus(false) .build(); partition_button.set_group(Some(&null_checkbutton)); let partition_row: adw::ActionRow = if partition_fs.contains("crypto_LUKS") || partition_fs.contains("lvm") { let prow = adw::ActionRow::builder() .activatable_widget(&partition_button) .title(partition.clone()) .name(partition.clone()) .subtitle("This partition needs a mapper!") .build(); prow } else { let prow = adw::ActionRow::builder() .activatable_widget(&partition_button) .title(partition.clone()) .name(partition.clone()) .subtitle(partition_fs + &pretty_bytes::converter::convert(partition_size)) .build(); prow }; partition_row.add_prefix(&partition_button); partition_button.connect_toggled(clone!(@weak row, @weak listbox, @weak partition_button, @strong manual_drive_mount_array, @strong partition=> move |_| { let mut manual_drive_mount_array_ref = RefCell::borrow_mut(&manual_drive_mount_array); if partition_button.is_active() == true { row.set_partition(partition.clone()); } else { let manual_drive_mount_array_ref_index = manual_drive_mount_array_ref.iter().position(|x| x.partition == partition.clone()).unwrap(); manual_drive_mount_array_ref.remove(manual_drive_mount_array_ref_index); } })); partition_scroll_child.append(&partition_row); } let listbox_clone = listbox.clone(); row.connect_closure( "row-deleted", false, closure_local!(@strong row as _row => move |_row: DriveMountRow| { listbox_clone.remove(&_row) }), ); // Return row row } //pub fn manual_partitioning(window: &adw::ApplicationWindow, partitioning_stack: >k::Stack, bottom_next_button: >k::Button) -> (gtk::TextBuffer, gtk::TextBuffer, adw::PasswordEntryRow) { pub fn manual_partitioning( partitioning_stack: >k::Stack, bottom_next_button: >k::Button, manual_drive_mount_array: &Rc>>, ) { let part_table_array: Rc>> = Default::default(); let check_part_unique = Rc::new(RefCell::new(true)); let partition_method_manual_main_box = gtk::Box::builder() .orientation(Orientation::Vertical) .margin_bottom(15) .margin_top(15) .margin_end(15) .margin_start(15) .build(); let partition_method_manual_header_box = gtk::Box::builder() .orientation(Orientation::Horizontal) .build(); // the header text for the partitioning page let partition_method_manual_header_text = gtk::Label::builder() .label("Manual Partitioning Installer") .halign(gtk::Align::End) .hexpand(true) .margin_top(15) .margin_bottom(15) .margin_start(15) .margin_end(5) .build(); partition_method_manual_header_text.add_css_class("header_sized_text"); // the header icon for the partitioning icon let partition_method_manual_header_icon = gtk::Image::builder() .icon_name("input-tablet") .halign(gtk::Align::Start) .hexpand(true) .pixel_size(78) .margin_top(15) .margin_bottom(15) .margin_start(0) .margin_end(15) .build(); let partition_method_manual_selection_box = gtk::Box::builder() .orientation(Orientation::Vertical) .build(); let partition_method_manual_gparted_button_content_box = gtk::Box::builder() .orientation(Orientation::Vertical) .build(); let partition_method_manual_gparted_button_content_text = gtk::Label::builder() .label("Use this utility to partition/mount/format your drives.") .build(); let partition_method_manual_gparted_button_content = adw::ButtonContent::builder() .label("Open GPARTED") .icon_name("gparted") .build(); let partition_method_manual_gparted_button = gtk::Button::builder() .child(&partition_method_manual_gparted_button_content_box) .halign(Align::Center) .valign(Align::Start) .build(); let drive_mounts_adw_listbox = gtk::ListBox::builder().hexpand(true).vexpand(true).build(); drive_mounts_adw_listbox.add_css_class("boxed-list"); let drive_mounts_viewport = gtk::ScrolledWindow::builder() .halign(Align::Center) .valign(Align::Center) .margin_top(30) .margin_bottom(30) .margin_start(30) .margin_end(30) .propagate_natural_height(true) .propagate_natural_width(true) .min_content_height(200) .min_content_width(200) .hexpand(true) .vexpand(true) .child(&drive_mounts_adw_listbox) .build(); let partition_method_manual_selection_text = gtk::Label::builder() .label("\n - Press the plus button below to begin adding filesystem entries.\nNotes:\n - This installer doesn't erase any data automatically, format your drives manually via gparted.\n - To Add a linux-swap partition set mountpoint to [SWAP]\n - We recommend the following partitions as a base layout:\n /boot ~ 1000mb ext4.\n /boot/efi ~ 512mb vfat/fat32.\n / >= 25GB btrfs.\n ") .halign(gtk::Align::Center) .hexpand(true) .margin_top(15) .margin_bottom(15) .margin_start(15) .margin_end(15) .build(); partition_method_manual_selection_text.add_css_class("medium_sized_text"); let partition_refresh_button = gtk::Button::builder() .label("Refresh Partition Table") .halign(gtk::Align::End) .build(); partition_refresh_button.add_css_class("destructive-action"); let fstab_valid_check = gtk::Button::builder() .label("Validate Filesystem Table") .halign(gtk::Align::Start) .build(); fstab_valid_check.add_css_class("valid-action"); let drive_mount_add_button = gtk::Button::builder() .icon_name("list-add") .vexpand(true) .hexpand(true) .build(); let partition_method_manual_error_label = gtk::Label::builder() .halign(Align::Start) .valign(Align::End) .vexpand(true) .visible(false) .build(); partition_method_manual_error_label.add_css_class("small_error_text"); let partition_method_manual_valid_label = gtk::Label::builder() .halign(Align::Start) .valign(Align::End) .vexpand(true) .visible(false) .label("Filesystem Table is valid!") .build(); partition_method_manual_valid_label.add_css_class("small_valid_text"); let partition_method_manual_warn_label = gtk::Label::builder() .halign(Align::Start) .valign(Align::End) .vexpand(true) .visible(false) .build(); partition_method_manual_warn_label.add_css_class("small_warn_text"); partition_method_manual_header_box.append(&partition_method_manual_header_text); partition_method_manual_header_box.append(&partition_method_manual_header_icon); partition_method_manual_selection_box.append(&partition_method_manual_selection_text); partition_method_manual_selection_box.append(&partition_refresh_button); partition_method_manual_main_box.append(&partition_method_manual_header_box); partition_method_manual_main_box.append(&partition_method_manual_selection_box); partition_method_manual_gparted_button_content_box .append(&partition_method_manual_gparted_button_content); partition_method_manual_gparted_button_content_box .append(&partition_method_manual_gparted_button_content_text); partition_method_manual_main_box.append(&partition_method_manual_gparted_button); drive_mounts_adw_listbox.append(&drive_mount_add_button); partition_method_manual_main_box.append(&drive_mounts_viewport); partition_method_manual_main_box.append(&fstab_valid_check); partition_method_manual_main_box.append(&partition_method_manual_error_label); partition_method_manual_main_box.append(&partition_method_manual_valid_label); partition_method_manual_main_box.append(&partition_method_manual_warn_label); fstab_valid_check.connect_clicked(clone!(@weak partition_method_manual_error_label, @weak partition_method_manual_valid_label, @strong manual_drive_mount_array, @strong check_part_unique => move |_| { partition_err_check(&partition_method_manual_error_label, &partition_method_manual_valid_label, &manual_drive_mount_array); })); partition_refresh_button.connect_clicked(clone!(@weak drive_mounts_adw_listbox,@strong part_table_array, @strong manual_drive_mount_array => move |_| { while let Some(row) = drive_mounts_adw_listbox.last_child() { if row.widget_name() == "DriveMountRow" { drive_mounts_adw_listbox.remove(&row); } else { break } } let partition_method_manual_get_partitions_lines = BufReader::new(cmd!("bash", "-c", "sudo /usr/lib/pika/pika-installer-gtk4/scripts/partition-utility.sh get_partitions").reader().unwrap()).lines(); let mut part_table_array_ref = part_table_array.borrow_mut(); part_table_array_ref.clear(); let mut manual_drive_mount_array_ref = manual_drive_mount_array.borrow_mut(); manual_drive_mount_array_ref.clear(); for partition in partition_method_manual_get_partitions_lines { part_table_array_ref.push(partition.unwrap()); } })); partition_refresh_button.emit_clicked(); partition_method_manual_gparted_button.connect_clicked(move |_| { Command::new("gparted") .spawn() .expect("gparted failed to start"); }); drive_mount_add_button.connect_clicked(clone!(@weak drive_mounts_adw_listbox, @strong manual_drive_mount_array, @strong part_table_array, @strong check_part_unique => move |_| { drive_mounts_adw_listbox.append(&create_mount_row(&drive_mounts_adw_listbox, &manual_drive_mount_array, &part_table_array,&check_part_unique)) })); let (anti_dup_partition_sender, anti_dup_partition_receiver) = async_channel::unbounded(); let anti_dup_partition_sender = anti_dup_partition_sender.clone(); // The long running operation runs now in a separate thread gio::spawn_blocking(move || loop { thread::sleep(Duration::from_millis(400)); anti_dup_partition_sender .send_blocking(true) .expect("The channel needs to be open."); }); let anti_dup_partition_loop_context = MainContext::default(); anti_dup_partition_loop_context.spawn_local(clone!(@weak drive_mounts_adw_listbox, @weak partitioning_stack, @strong manual_drive_mount_array,@weak bottom_next_button, @strong check_part_unique => async move { while let Ok(_state) = anti_dup_partition_receiver.recv().await { let mut counter = drive_mounts_adw_listbox.first_child(); let mut manual_drive_mount_array_ref = manual_drive_mount_array.borrow_mut(); // usage of while loop manual_drive_mount_array_ref.clear(); while let Some(row) = counter { if row.widget_name() == "DriveMountRow" { let row_mount = DriveMount { partition: row.clone().property("partition"), mountpoint: row.clone().property("mountpoint"), mountopt: row.clone().property("mountopt"), }; manual_drive_mount_array_ref.push(row_mount); } counter = row.next_sibling(); } let mut counter = drive_mounts_adw_listbox.first_child(); while let Some(ref row) = counter { if row.widget_name() == "DriveMountRow" { let mut counter_scrw = row.property::("partitionscroll").child().unwrap().first_child().unwrap().first_child(); while let Some(ref row_scrw) = counter_scrw { if manual_drive_mount_array_ref.iter().any(|e| { if !e.partition.is_empty() { row_scrw.widget_name().contains(&e.partition) } else { return false } }) { if *check_part_unique.borrow_mut() == true { row_scrw.set_sensitive(false) } else if row_scrw.property::("subtitle").contains("This partition needs a mapper!") { row_scrw.set_sensitive(false) } else { row_scrw.set_sensitive(true) } } else if row_scrw.property::("subtitle").contains("This partition needs a mapper!") { row_scrw.set_sensitive(false) } else { row_scrw.set_sensitive(true) } counter_scrw = row_scrw.next_sibling(); } } counter = row.next_sibling(); } let manual_drive_mount_array_ref_clone = manual_drive_mount_array_ref.clone(); *check_part_unique.borrow_mut() = true; for mountopts in manual_drive_mount_array_ref .iter() .map(|x| x.mountopt.as_str()) .collect::>() { if mountopts.contains("subvol") { *check_part_unique.borrow_mut() = false } } if *check_part_unique.borrow_mut() == false { partition_method_manual_warn_label .set_label("Partition reuse check will be skipped due to subvol usage."); partition_method_manual_warn_label.set_visible(true); } else { partition_method_manual_warn_label.set_visible(false); } if partitioning_stack.visible_child_name() == Some(GString::from_string_unchecked("partition_method_manual_page".into())) { if manual_drive_mount_array_ref_clone.iter().any(|x| {if x.mountpoint == "/" {return true} else {return false}}) && manual_drive_mount_array_ref_clone.iter().any(|x| {if x.mountpoint == "/boot" {return true} else {return false}}) && manual_drive_mount_array_ref_clone.iter().any(|x| {if x.mountpoint == "/boot/efi" {return true} else {return false}}) && !partition_method_manual_error_label.is_visible() && partition_method_manual_valid_label.is_visible() { if !bottom_next_button.is_sensitive() { bottom_next_button.set_sensitive(true); } } else { if bottom_next_button.is_sensitive() { bottom_next_button.set_sensitive(false); } } } } })); partitioning_stack.add_titled( &partition_method_manual_main_box, Some("partition_method_manual_page"), "partition_method_manual_page", ); //return(partition_method_manual_target_buffer, partition_method_manual_luks_buffer, partition_method_manual_luks_password_entry) } fn partition_err_check( partition_method_manual_error_label: >k::Label, partition_method_manual_valid_label: >k::Label, manual_drive_mount_array: &Rc>>, ) { let mut empty_mountpoint = false; let manual_drive_mount_array_ref = manual_drive_mount_array.borrow_mut(); for mountpoint in manual_drive_mount_array_ref .iter() .map(|x| x.mountpoint.as_str()) .collect::>() { if empty_mountpoint == false { if mountpoint.is_empty() { empty_mountpoint = true } } } let mut empty_partition = false; for partition in manual_drive_mount_array_ref .iter() .map(|x| x.partition.as_str()) .collect::>() { if empty_partition == false { if partition.is_empty() { empty_partition = true } } } if empty_mountpoint == false { if &partition_method_manual_error_label.label() == "Some drives don't have a mountpoint configured." { partition_method_manual_error_label.set_visible(false); } if manual_drive_mount_array_ref.len() - manual_drive_mount_array_ref .iter() .map(|x| x.mountpoint.as_str()) .collect::>() .len() > 0 { if !partition_method_manual_error_label.is_visible() { partition_method_manual_error_label .set_label("Multiple drives were mounted to the same mountpoint."); partition_method_manual_error_label.set_visible(true); } } else { if partition_method_manual_error_label.label() == "Multiple drives were mounted to the same mountpoint." { partition_method_manual_error_label.set_visible(false); } } } else { if !partition_method_manual_error_label.is_visible() { partition_method_manual_error_label .set_label("Some drives don't have a mountpoint configured."); partition_method_manual_error_label.set_visible(true); } } if empty_partition == true { if !partition_method_manual_error_label.is_visible() { partition_method_manual_error_label .set_label("There's a drive row without a partition."); partition_method_manual_error_label.set_visible(true); } } else { if partition_method_manual_error_label.label() == "There's a drive row without a partition." { partition_method_manual_error_label.set_visible(false); } } for drivemounts in manual_drive_mount_array_ref .iter() .map(|x| x) .collect::>() { if !drivemounts.partition.is_empty() { let partition_size_cli = Command::new("sudo") .arg("/usr/lib/pika/pika-installer-gtk4/scripts/partition-utility.sh") .arg("get_part_size") .arg(drivemounts.partition.clone()) .output() .expect("failed to execute process"); let partition_fs_cli = Command::new("sudo") .arg("/usr/lib/pika/pika-installer-gtk4/scripts/partition-utility.sh") .arg("get_part_fs") .arg(drivemounts.partition.replace("mapper/", "")) .output() .expect("failed to execute process"); let partition_size = String::from_utf8(partition_size_cli.stdout) .expect("Failed to create float") .trim() .parse::() .unwrap(); let partition_fs = String::from_utf8(partition_fs_cli.stdout) .expect("Failed to create string") .trim() .parse::() .unwrap(); if drivemounts.mountpoint == "/boot/efi" { if partition_size < 500000000.0 { if !partition_method_manual_error_label.is_visible() { partition_method_manual_error_label.set_label( &("Small size: The partition mounted to /boot/efi (/dev/".to_owned() + &drivemounts.partition + ") Must at least be 512MBs"), ); partition_method_manual_error_label.set_visible(true); } } else { if partition_method_manual_error_label .label() .contains("Small size: The partition mounted to /boot/efi (/dev/") { partition_method_manual_error_label.set_visible(false); } } if partition_fs != "vfat" { if !partition_method_manual_error_label.is_visible() { partition_method_manual_error_label.set_label( &("Bad Filesystem: The partition mounted to /boot/efi (/dev/" .to_owned() + &drivemounts.partition + ") Must at be FAT32/vFAT"), ); partition_method_manual_error_label.set_visible(true); } } else { if partition_method_manual_error_label .label() .contains("Bad Filesystem: The partition mounted to /boot/efi (/dev/") { partition_method_manual_error_label.set_visible(false); } } } if drivemounts.mountpoint == "/boot" { if partition_size < 1000000000.0 { if !partition_method_manual_error_label.is_visible() { partition_method_manual_error_label.set_label( &("Small size: The partition mounted to /boot (/dev/".to_owned() + &drivemounts.partition + ") Must at least be 1000MBs"), ); partition_method_manual_error_label.set_visible(true); } } else { if partition_method_manual_error_label .label() .contains("Small size: The partition mounted to /boot (/dev/") { partition_method_manual_error_label.set_visible(false); } } if partition_fs == "vfat" { if !partition_method_manual_error_label.is_visible() { partition_method_manual_error_label.set_label( &("Bad Filesystem: The partition mounted to /boot (/dev/".to_owned() + &drivemounts.partition + ") Cannot be FAT32/vFAT"), ); partition_method_manual_error_label.set_visible(true); } } else { if partition_method_manual_error_label .label() .contains("Bad Filesystem: The partition mounted to /boot (/dev/") { partition_method_manual_error_label.set_visible(false); } } } if drivemounts.mountpoint == "/" { if partition_size < 25000000000.0 { if !partition_method_manual_error_label.is_visible() { partition_method_manual_error_label.set_label( &("Small size: The partition mounted to / (/dev/".to_owned() + &drivemounts.partition + ") Must at least be 25GBs"), ); partition_method_manual_error_label.set_visible(true); } } else { if partition_method_manual_error_label .label() .contains("Small size: The partition mounted to / (/dev/") { partition_method_manual_error_label.set_visible(false); } } if partition_fs == "vfat" { if !partition_method_manual_error_label.is_visible() { partition_method_manual_error_label.set_label( &("Bad Filesystem: The partition mounted to / (/dev/".to_owned() + &drivemounts.partition + ") Cannot be FAT32/vFAT"), ); partition_method_manual_error_label.set_visible(true); } } else { if partition_method_manual_error_label .label() .contains("Bad Filesystem: The partition mounted to / (/dev/") { partition_method_manual_error_label.set_visible(false); } } } if drivemounts.mountpoint == "/home" { if partition_size < 10000000000.0 { if !partition_method_manual_error_label.is_visible() { partition_method_manual_error_label.set_label( &("Small size: The partition mounted to /home (/dev/".to_owned() + &drivemounts.partition + ") Must at least be 10GBs"), ); partition_method_manual_error_label.set_visible(true); } } else { if partition_method_manual_error_label .label() .contains("Small size: The partition mounted to /home (/dev/") { partition_method_manual_error_label.set_visible(false); } } if partition_fs == "vfat" { if !partition_method_manual_error_label.is_visible() { partition_method_manual_error_label.set_label( &("Bad Filesystem: The partition mounted to /home (/dev/".to_owned() + &drivemounts.partition + ") Cannot be FAT32/vFAT"), ); partition_method_manual_error_label.set_visible(true); } } else { if partition_method_manual_error_label .label() .contains("Bad Filesystem: The partition mounted to /home (/dev/") { partition_method_manual_error_label.set_visible(false); } } } if drivemounts.mountpoint == "[SWAP]" { if partition_fs != "swap" { if !partition_method_manual_error_label.is_visible() { partition_method_manual_error_label.set_label( &("Bad Filesystem: ".to_owned() + &drivemounts.partition + " Is not a swap partition"), ); partition_method_manual_error_label.set_visible(true); } } else { if partition_method_manual_error_label .label() .contains(" Is not a swap partition") { partition_method_manual_error_label.set_visible(false); } } } if empty_mountpoint == false && !drivemounts.mountpoint.starts_with("/") && drivemounts.mountpoint != "[SWAP]" { if !partition_method_manual_error_label.is_visible() { partition_method_manual_error_label.set_label( &("Bad Mountpoint: ".to_owned() + &drivemounts.mountpoint + " Is not a valid mountpoint"), ); partition_method_manual_error_label.set_visible(true); } } else { if partition_method_manual_error_label .label() .contains(" Is not a valid mountpoint") { partition_method_manual_error_label.set_visible(false); } } if !partition_method_manual_error_label.is_visible() { partition_method_manual_valid_label.set_visible(true) } else { partition_method_manual_valid_label.set_visible(false) } } } }