diff --git a/Cargo.lock b/Cargo.lock index 3a98614..a1bd3bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1592,9 +1592,9 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" dependencies = [ "lazy_static", "libc", @@ -1617,7 +1617,7 @@ checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" [[package]] name = "nix-data" version = "0.0.2" -source = "git+https://github.com/snowflakelinux/nix-data#33479e595f14142f15fb6ec29cc585352c2c7703" +source = "git+https://github.com/snowflakelinux/nix-data#315d3efee6860cdc48e514ac1fa288196312a4f4" dependencies = [ "anyhow", "csv", @@ -1742,9 +1742,9 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.13.1" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" dependencies = [ "hermit-abi", "libc", diff --git a/default.nix b/default.nix index 9532821..e3a8b75 100644 --- a/default.nix +++ b/default.nix @@ -20,7 +20,7 @@ pkgs.stdenv.mkDerivation rec { cargoDeps = pkgs.rustPlatform.fetchCargoTarball { inherit src; name = "${pname}-${version}"; - hash = "sha256-CRw/T0TAgW+81C3966PAEpDHJotSbOHkAqhotY1sozM="; + hash = "sha256-yChtK86/onSI2JQaQkBwwVzijGTEyc6vQbCUmgWb3u8="; }; nativeBuildInputs = with pkgs; [ diff --git a/nsc-helper/src/main.rs b/nsc-helper/src/main.rs index 91d97a5..065d7fa 100644 --- a/nsc-helper/src/main.rs +++ b/nsc-helper/src/main.rs @@ -1,7 +1,7 @@ use clap::{self, FromArgMatches, Subcommand}; use std::{ error::Error, - fs::{File, self}, + fs::{self, File}, io::{self, Read, Write}, process::Command, }; @@ -23,6 +23,12 @@ enum SubCommands { /// Whether to rebuild the system after updating channels #[arg(short, long)] rebuild: bool, + /// Update file + #[arg(short, long)] + update: bool, + /// Write stdin to file in path output + #[arg(short, long)] + output: String, /// Run `nixos-rebuild` with the given arguments arguments: Vec, }, @@ -33,6 +39,12 @@ enum SubCommands { /// Path to the flake file #[arg(short, long)] flakepath: String, + /// Update file + #[arg(short, long)] + update: bool, + /// Write stdin to file in path output + #[arg(short, long)] + output: String, /// Run `nixos-rebuild` with the given arguments arguments: Vec, }, @@ -81,7 +93,18 @@ fn main() { std::process::exit(1); } }, - SubCommands::Channel { rebuild: dorebuild, arguments } => { + SubCommands::Channel { + rebuild: dorebuild, + update, + output, + arguments, + } => { + if update { + if let Err(e) = write_file(&output) { + eprintln!("{}", e); + std::process::exit(1); + } + } match channel() { Ok(_) => { if dorebuild { @@ -93,14 +116,26 @@ fn main() { } } } - }, + } Err(err) => { eprintln!("{}", err); std::process::exit(1); } } - }, - SubCommands::Flake { rebuild: dorebuild, flakepath, arguments } => { + } + SubCommands::Flake { + rebuild: dorebuild, + flakepath, + update, + output, + arguments, + } => { + if update { + if let Err(e) = write_file(&output) { + eprintln!("{}", e); + std::process::exit(1); + } + } match flake(&flakepath) { Ok(_) => { if dorebuild { @@ -112,7 +147,7 @@ fn main() { } } } - }, + } Err(err) => { eprintln!("{}", err); std::process::exit(1); @@ -132,9 +167,7 @@ fn write_file(path: &str) -> Result<(), Box> { } fn rebuild(args: Vec) -> Result<(), Box> { - let mut cmd = Command::new("nixos-rebuild") - .args(args) - .spawn()?; + let mut cmd = Command::new("nixos-rebuild").args(args).spawn()?; let x = cmd.wait()?; if x.success() { Ok(()) @@ -148,9 +181,7 @@ fn rebuild(args: Vec) -> Result<(), Box> { } fn channel() -> Result<(), Box> { - let mut cmd = Command::new("nix-channel") - .arg("--update") - .spawn()?; + let mut cmd = Command::new("nix-channel").arg("--update").spawn()?; let x = cmd.wait()?; if x.success() { Ok(()) @@ -179,4 +210,4 @@ fn flake(path: &str) -> Result<(), Box> { "nix flake failed", ))) } -} \ No newline at end of file +} diff --git a/src/ui/installworker.rs b/src/ui/installworker.rs index 17ba4a1..91adfb6 100644 --- a/src/ui/installworker.rs +++ b/src/ui/installworker.rs @@ -135,6 +135,7 @@ impl Worker for InstallAsyncHandler { .arg("profile") .arg("install") .arg(format!("nixpkgs#{}", work.pkg)) + .arg("--impure") .kill_on_drop(true) .stdout(Stdio::piped()) .stderr(Stdio::piped()) diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 5fe4d42..f7d8fcf 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -10,6 +10,7 @@ pub mod preferencespage; pub mod rebuild; pub mod screenshotfactory; pub mod searchpage; +pub mod unavailabledialog; pub mod updatepage; pub mod updateworker; pub mod welcome; diff --git a/src/ui/unavailabledialog.rs b/src/ui/unavailabledialog.rs new file mode 100644 index 0000000..714bf53 --- /dev/null +++ b/src/ui/unavailabledialog.rs @@ -0,0 +1,264 @@ +use std::path::Path; + +use gtk::pango; +use log::*; +use relm4::{*, prelude::*, factory::*}; +use adw::prelude::*; +use crate::{APPINFO, ui::{window::REBUILD_BROKER, rebuild::RebuildMsg}}; + +use super::updatepage::{UpdatePageMsg, UpdateType}; + +#[derive(Debug)] +pub struct UnavailableDialogModel { + hidden: bool, + unavailableuseritems: FactoryVecDeque, + unavailablesysitems: FactoryVecDeque, + updatetype: UpdateType, +} + +#[derive(Debug)] +pub enum UnavailableDialogMsg { + Show(Vec, Vec, UpdateType), + Close, + Continue, +} + +#[relm4::component(pub)] +impl SimpleComponent for UnavailableDialogModel { + type Init = gtk::Window; + type Input = UnavailableDialogMsg; + type Output = UpdatePageMsg; + type Widgets = UnavailableDialogWidgets; + + view! { + dialog = adw::MessageDialog { + #[watch] + set_visible: !model.hidden, + set_transient_for: Some(&parent_window), + set_modal: true, + set_heading: Some("Some packages are unavailable!"), + set_body: "If you continue this update, some packages will be removed", + #[wrap(Some)] + set_extra_child = >k::Box { + set_orientation: gtk::Orientation::Vertical, + set_spacing: 20, + adw::PreferencesGroup { + #[watch] + set_visible: !model.unavailableuseritems.is_empty(), + set_title: "User Packages", + #[local_ref] + unavailableuserlist -> gtk::ListBox { + add_css_class: "boxed-list", + set_selection_mode: gtk::SelectionMode::None, + }, + }, + adw::PreferencesGroup { + #[watch] + set_visible: !model.unavailablesysitems.is_empty(), + set_title: "System Packages", + #[local_ref] + unavailablesyslist -> gtk::ListBox { + add_css_class: "boxed-list", + set_selection_mode: gtk::SelectionMode::None + }, + } + }, + add_response: ("cancel", "Cancel"), + add_response: ("continue", "Continue"), + set_response_appearance: ("continue", adw::ResponseAppearance::Destructive), + connect_close_request => |_| { + gtk::Inhibit(true) + } + } + } + + fn init( + parent_window: Self::Init, + root: &Self::Root, + sender: ComponentSender, + ) -> ComponentParts { + + let model = UnavailableDialogModel { + unavailableuseritems: FactoryVecDeque::new(gtk::ListBox::new(), sender.input_sender()), + unavailablesysitems: FactoryVecDeque::new(gtk::ListBox::new(), sender.input_sender()), + updatetype: UpdateType::All, + hidden: true, + }; + + let unavailableuserlist = model.unavailableuseritems.widget(); + let unavailablesyslist = model.unavailablesysitems.widget(); + + let widgets = view_output!(); + + widgets.dialog.connect_response(None, move |_, resp| { + match resp { + "cancel" => { + REBUILD_BROKER.send(RebuildMsg::Close); + debug!("Response: cancel") + }, + "continue" => { + sender.input(UnavailableDialogMsg::Continue); + debug!("Response: continue") + }, + _ => unreachable!(), + } + }); + ComponentParts { model, widgets } + + } + + fn update(&mut self, msg: Self::Input, sender: ComponentSender) { + match msg { + UnavailableDialogMsg::Show(useritems, sysitems, updatetype) => { + self.updatetype = updatetype; + let mut unavailableuseritems_guard = self.unavailableuseritems.guard(); + unavailableuseritems_guard.clear(); + for item in useritems { + unavailableuseritems_guard.push_back(item); + } + let mut unavailablesysitems_guard = self.unavailablesysitems.guard(); + for item in sysitems { + unavailablesysitems_guard.push_back(item); + } + self.hidden = false; + } + UnavailableDialogMsg::Close => { + info!("UpdateDialogMsg::Close"); + let mut unavailableuseritems_guard = self.unavailableuseritems.guard(); + let mut unavailablesysitems_guard = self.unavailablesysitems.guard(); + unavailableuseritems_guard.clear(); + unavailablesysitems_guard.clear(); + self.hidden = true; + } + UnavailableDialogMsg::Continue => { + match self.updatetype { + UpdateType::User => { + sender.output(UpdatePageMsg::UpdateAllUserRm(self.unavailableuseritems.iter().map(|x| x.pkg.to_string()).collect())); + } + UpdateType::System => { + sender.output(UpdatePageMsg::UpdateSystemRm(self.unavailablesysitems.iter().map(|x| x.pkg.to_string()).collect())); + } + UpdateType::All => { + sender.output(UpdatePageMsg::UpdateAllRm(self.unavailableuseritems.iter().map(|x| x.pkg.to_string()).collect(), self.unavailablesysitems.iter().map(|x| x.pkg.to_string()).collect())); + } + } + sender.input(UnavailableDialogMsg::Close) + } + } + } +} + +#[derive(Default, Debug, PartialEq, Eq)] +pub struct UnavailableItemModel { + pub name: String, + pub pkg: String, + pub pname: String, + pub icon: Option, + pub message: String, +} + +#[derive(Debug)] +pub enum UnavailableItemMsg {} + +#[relm4::factory(pub)] +impl FactoryComponent for UnavailableItemModel { + type CommandOutput = (); + type Init = UnavailableItemModel; + type Input = (); + type Output = UnavailableItemMsg; + type Widgets = UnavailableItemWidgets; + type ParentWidget = adw::gtk::ListBox; + type ParentInput = UnavailableDialogMsg; + + view! { + adw::PreferencesRow { + set_activatable: false, + #[wrap(Some)] + set_child = >k::Box { + set_orientation: gtk::Orientation::Horizontal, + set_hexpand: true, + set_spacing: 10, + set_margin_all: 10, + adw::Bin { + set_valign: gtk::Align::Center, + #[wrap(Some)] + set_child = if self.icon.is_some() { + gtk::Image { + add_css_class: "icon-dropshadow", + set_halign: gtk::Align::Start, + set_from_file: { + if let Some(i) = &self.icon { + let iconpath = format!("{}/icons/nixos/128x128/{}", APPINFO, i); + let iconpath64 = format!("{}/icons/nixos/64x64/{}", APPINFO, i); + if Path::new(&iconpath).is_file() { + Some(iconpath) + } else if Path::new(&iconpath64).is_file() { + Some(iconpath64) + } else { + None + } + } else { + None + } + }, + set_pixel_size: 64, + } + } else { + gtk::Image { + add_css_class: "icon-dropshadow", + set_halign: gtk::Align::Start, + set_icon_name: Some("package-x-generic"), + set_pixel_size: 64, + } + } + }, + gtk::Box { + set_orientation: gtk::Orientation::Horizontal, + set_halign: gtk::Align::Fill, + set_valign: gtk::Align::Center, + set_hexpand: true, + set_spacing: 20, + gtk::Box { + set_orientation: gtk::Orientation::Vertical, + set_halign: gtk::Align::Fill, + set_valign: gtk::Align::Center, + set_spacing: 2, + gtk::Label { + set_halign: gtk::Align::Start, + set_label: self.name.as_str(), + set_ellipsize: pango::EllipsizeMode::End, + set_lines: 1, + set_wrap: true, + set_max_width_chars: 0, + }, + gtk::Label { + set_halign: gtk::Align::Start, + add_css_class: "dim-label", + add_css_class: "caption", + set_label: self.pkg.as_str(), + set_ellipsize: pango::EllipsizeMode::End, + set_lines: 1, + set_wrap: true, + set_max_width_chars: 0, + }, + }, + gtk::Label { + set_halign: gtk::Align::Center, + set_hexpand: true, + set_label: self.message.as_str(), + set_wrap: true, + } + } + + } + } + } + + fn init_model( + init: Self::Init, + _index: &DynamicIndex, + _sender: FactoryComponentSender, + ) -> Self { + init + } +} diff --git a/src/ui/updatepage.rs b/src/ui/updatepage.rs index 9c15721..4112429 100644 --- a/src/ui/updatepage.rs +++ b/src/ui/updatepage.rs @@ -1,12 +1,14 @@ -use crate::APPINFO; +use crate::{APPINFO, ui::unavailabledialog::UnavailableDialogModel}; use super::{pkgpage::InstallType, window::*, updateworker::{UpdateAsyncHandler, UpdateAsyncHandlerMsg, UpdateAsyncHandlerInit}, rebuild::RebuildMsg}; use adw::prelude::*; use nix_data::config::configfile::NixDataConfig; use relm4::{factory::*, gtk::pango, *}; -use std::{path::Path, convert::identity}; +use std::{path::Path, convert::identity, collections::HashMap}; use log::*; +pub static UNAVAILABLE_BROKER: MessageBroker = MessageBroker::new(); + #[tracker::track] #[derive(Debug)] pub struct UpdatePageModel { @@ -21,6 +23,8 @@ pub struct UpdatePageModel { systype: SystemPkgs, usertype: UserPkgs, updatetracker: u8, + #[tracker::no_eq] + unavailabledialog: Controller, } #[derive(Debug)] @@ -30,15 +34,25 @@ pub enum UpdatePageMsg { Update(Vec, Vec), OpenRow(usize, InstallType), UpdateSystem, + UpdateSystemRm(Vec), UpdateAllUser, + UpdateAllUserRm(Vec), UpdateUser(String), // UpdateChannels, // UpdateSystemAndChannels, UpdateAll, + UpdateAllRm(Vec, Vec), DoneWorking, FailedWorking, } +#[derive(Debug)] +pub enum UpdateType { + System, + User, + All, +} + pub struct UpdatePageInit { pub window: gtk::Window, pub systype: SystemPkgs, @@ -84,101 +98,6 @@ impl SimpleComponent for UpdatePageModel { } } }, - // gtk::Box { - // set_orientation: gtk::Orientation::Horizontal, - // set_hexpand: true, - // #[watch] - // set_visible: model.channelupdate.is_some(), - // gtk::Label { - // set_halign: gtk::Align::Start, - // add_css_class: "title-4", - // set_label: "Channels", - // }, - // }, - // gtk::ListBox { - // set_valign: gtk::Align::Start, - // add_css_class: "boxed-list", - // set_selection_mode: gtk::SelectionMode::None, - // #[watch] - // set_visible: model.channelupdate.is_some(), - // adw::PreferencesRow { - // set_activatable: false, - // set_can_focus: false, - // #[wrap(Some)] - // set_child = >k::Box { - // set_orientation: gtk::Orientation::Horizontal, - // set_hexpand: true, - // set_spacing: 10, - // set_margin_all: 10, - // adw::Bin { - // set_valign: gtk::Align::Center, - // gtk::Image { - // add_css_class: "icon-dropshadow", - // set_halign: gtk::Align::Start, - // set_icon_name: Some("application-x-addon"), - // set_pixel_size: 64, - // } - // }, - // gtk::Box { - // set_orientation: gtk::Orientation::Vertical, - // set_halign: gtk::Align::Fill, - // set_valign: gtk::Align::Center, - // set_hexpand: true, - // set_spacing: 2, - // gtk::Label { - // set_halign: gtk::Align::Start, - // set_label: "nixos", - // set_ellipsize: pango::EllipsizeMode::End, - // set_lines: 1, - // set_wrap: true, - // set_max_width_chars: 0, - // }, - // gtk::Label { - // set_halign: gtk::Align::Start, - // add_css_class: "dim-label", - // add_css_class: "caption", - // set_label: { - // &(if let Some((old, new)) = &model.channelupdate { - // format!("{} → {}", old, new) - // } else { - // String::default() - // }) - // }, - // set_visible: model.channelupdate.is_some(), - // set_ellipsize: pango::EllipsizeMode::End, - // set_lines: 1, - // set_wrap: true, - // set_max_width_chars: 0, - // }, - // }, - // gtk::Box { - // set_orientation: gtk::Orientation::Vertical, - // set_spacing: 5, - // set_halign: gtk::Align::End, - // set_valign: gtk::Align::Center, - // gtk::Button { - // add_css_class: "suggested-action", - // set_valign: gtk::Align::Center, - // set_halign: gtk::Align::End, - // set_label: "Update channel and system", - // set_can_focus: false, - // connect_clicked[sender] => move |_| { - // sender.input(UpdatePageMsg::UpdateSystemAndChannels); - // } - // }, - // gtk::Button { - // set_valign: gtk::Align::Center, - // set_halign: gtk::Align::End, - // set_label: "Update channel only", - // set_can_focus: false, - // connect_clicked[sender] => move |_| { - // sender.input(UpdatePageMsg::UpdateChannels); - // } - // }, - // } - // } - // } - // }, gtk::Box { set_orientation: gtk::Orientation::Horizontal, set_hexpand: true, @@ -283,6 +202,10 @@ impl SimpleComponent for UpdatePageModel { .detach_worker(UpdateAsyncHandlerInit { syspkgs: initparams.systype.clone(), userpkgs: initparams.usertype.clone() }) .forward(sender.input_sender(), identity); + let unavailabledialog = UnavailableDialogModel::builder() + .launch_with_broker(initparams.window.clone(), &UNAVAILABLE_BROKER) + .forward(sender.input_sender(), identity); + let config = initparams.config; updateworker.emit(UpdateAsyncHandlerMsg::UpdateConfig(config.clone())); @@ -295,6 +218,7 @@ impl SimpleComponent for UpdatePageModel { config, systype: initparams.systype, usertype: initparams.usertype, + unavailabledialog, tracker: 0, }; @@ -322,13 +246,6 @@ impl SimpleComponent for UpdatePageModel { info!("UpdatePageMsg::Update"); debug!("UPDATEUSERLIST: {:?}", updateuserlist); debug!("UPDATESYSTEMLIST: {:?}", updatesystemlist); - // self.channelupdate = match nix_data::cache::channel::uptodate() { - // Ok(x) => { - // x - // }, - // Err(_) => None, - // }; - // debug!("CHANNELUPDATE: {:?}", self.channelupdate); self.update_updatetracker(|_| ()); let mut updateuserlist_guard = self.updateuserlist.guard(); updateuserlist_guard.clear(); @@ -340,16 +257,6 @@ impl SimpleComponent for UpdatePageModel { for updatesystem in updatesystemlist { updatesystemlist_guard.push_back(updatesystem); } - updatesystemlist_guard.push_back(UpdateItem { - pkg: None, - name: "NixOS".to_string(), - pname: "nixos".to_string(), - summary: None, - icon: None, - pkgtype: InstallType::System, - verfrom: None, - verto: None, - }); } UpdatePageMsg::OpenRow(row, pkgtype) => match pkgtype { InstallType::User => { @@ -369,17 +276,33 @@ impl SimpleComponent for UpdatePageModel { } } }, - // UpdatePageMsg::UpdateChannels => { - // self.updatedialog.emit(UpdateDialogMsg::Show(String::from("Updating channels..."))); - // self.updateworker.emit(UpdateAsyncHandlerMsg::UpdateChannels); - // } - // UpdatePageMsg::UpdateSystemAndChannels => { - // self.updatedialog.emit(UpdateDialogMsg::Show(String::from("Updating system and channels..."))); - // self.updateworker.emit(UpdateAsyncHandlerMsg::UpdateChannelsAndSystem); - // } UpdatePageMsg::UpdateSystem => { + let systype = self.systype.clone(); + let systemconfig = self.config.systemconfig.clone(); + let workersender = self.updateworker.sender().clone(); + let output = sender.output_sender().clone(); REBUILD_BROKER.send(RebuildMsg::Show); - self.updateworker.emit(UpdateAsyncHandlerMsg::UpdateSystem); + relm4::spawn(async move { + let uninstallsys = match systype { + SystemPkgs::Legacy => { + nix_data::cache::channel::unavailablepkgs(&[&systemconfig.unwrap()]).await.unwrap_or_default() + } + SystemPkgs::Flake => { + nix_data::cache::flakes::unavailablepkgs(&[&systemconfig.unwrap()]).await.unwrap_or_default() + } + _ => HashMap::new(), + }; + if uninstallsys.is_empty() { + workersender.send(UpdateAsyncHandlerMsg::UpdateSystem); + } else { + warn!("Uninstalling unavailable packages: {:?}", uninstallsys); + output.send(AppMsg::GetUnavailableItems(HashMap::new(), uninstallsys, UpdateType::System)); + } + }); + } + UpdatePageMsg::UpdateSystemRm(pkgs) => { + info!("UpdatePageMsg::UpdateSystemRm({:?})", pkgs); + self.updateworker.emit(UpdateAsyncHandlerMsg::UpdateSystemRemove(pkgs)); } UpdatePageMsg::UpdateUser(pkg) => { info!("UPDATE USER PKG: {}", pkg); @@ -387,11 +310,62 @@ impl SimpleComponent for UpdatePageModel { } UpdatePageMsg::UpdateAllUser => { REBUILD_BROKER.send(RebuildMsg::Show); - self.updateworker.emit(UpdateAsyncHandlerMsg::UpdateUserPkgs); + if self.usertype == UserPkgs::Profile { + let workersender = self.updateworker.sender().clone(); + let output = sender.output_sender().clone(); + relm4::spawn(async move { + let uninstalluser = nix_data::cache::profile::unavailablepkgs().await.unwrap_or_default(); + if uninstalluser.is_empty() { + workersender.send(UpdateAsyncHandlerMsg::UpdateUserPkgs); + } else { + warn!("Uninstalling unavailable packages: {:?}", uninstalluser); + output.send(AppMsg::GetUnavailableItems(uninstalluser, HashMap::new(), UpdateType::User)); + } + }); + + } else { + self.updateworker.emit(UpdateAsyncHandlerMsg::UpdateUserPkgs); + } + } + UpdatePageMsg::UpdateAllUserRm(pkgs) => { + info!("UpdatePageMsg::UpdateAllUserRm({:?})", pkgs); + self.updateworker.emit(UpdateAsyncHandlerMsg::UpdateUserPkgsRemove(pkgs)); } UpdatePageMsg::UpdateAll => { + info!("UpdatePageMsg::UpdateAll"); + let systype = self.systype.clone(); + let usertype = self.usertype.clone(); + let systemconfig = self.config.systemconfig.clone(); + let workersender = self.updateworker.sender().clone(); + let output = sender.output_sender().clone(); REBUILD_BROKER.send(RebuildMsg::Show); - self.updateworker.emit(UpdateAsyncHandlerMsg::UpdateAll); + relm4::spawn(async move { + let uninstallsys = match systype { + SystemPkgs::Legacy => { + nix_data::cache::channel::unavailablepkgs(&[&systemconfig.unwrap()]).await.unwrap_or_default() + } + SystemPkgs::Flake => { + nix_data::cache::flakes::unavailablepkgs(&[&systemconfig.unwrap()]).await.unwrap_or_default() + } + _ => HashMap::new(), + }; + let uninstalluser = if usertype == UserPkgs::Profile { + nix_data::cache::profile::unavailablepkgs().await.unwrap_or_default() + } else { + HashMap::new() + }; + if uninstallsys.is_empty() && uninstalluser.is_empty() { + workersender.send(UpdateAsyncHandlerMsg::UpdateAll); + } else { + warn!("Uninstalling unavailable user packages: {:?}", uninstalluser); + warn!("Uninstalling unavailable system packages: {:?}", uninstallsys); + output.send(AppMsg::GetUnavailableItems(uninstalluser, uninstallsys, UpdateType::All)); + } + }); + } + UpdatePageMsg::UpdateAllRm(userpkgs, syspkgs) => { + info!("UpdatePageMsg::UpdateAllRm({:?}, {:?})", userpkgs, syspkgs); + self.updateworker.emit(UpdateAsyncHandlerMsg::UpdateAllRemove(userpkgs, syspkgs)); } UpdatePageMsg::DoneWorking => { REBUILD_BROKER.send(RebuildMsg::FinishSuccess); diff --git a/src/ui/updateworker.rs b/src/ui/updateworker.rs index 1bb509c..329cc2a 100644 --- a/src/ui/updateworker.rs +++ b/src/ui/updateworker.rs @@ -1,9 +1,9 @@ +use anyhow::{anyhow, Result}; +use log::*; use nix_data::config::configfile::NixDataConfig; use relm4::*; -use std::{path::Path, process::Stdio}; -use tokio::io::AsyncBufReadExt; -use anyhow::Result; -use log::*; +use std::{fs, path::Path, process::Stdio}; +use tokio::io::{AsyncBufReadExt, AsyncWriteExt}; use crate::ui::{rebuild::RebuildMsg, window::REBUILD_BROKER}; @@ -17,7 +17,7 @@ use super::{ pub struct UpdateAsyncHandler { #[tracker::no_eq] process: Option>, - systemconfig: Option, + systemconfig: String, flakeargs: Option, syspkgs: SystemPkgs, userpkgs: UserPkgs, @@ -30,13 +30,15 @@ pub enum UpdateAsyncHandlerMsg { // UpdateChannels, // UpdateChannelsAndSystem, - UpdateSystem, + UpdateSystemRemove(Vec), RebuildSystem, UpdateUserPkgs, + UpdateUserPkgsRemove(Vec), UpdateAll, + UpdateAllRemove(Vec, Vec), } enum NscCmd { @@ -58,7 +60,7 @@ impl Worker for UpdateAsyncHandler { fn init(params: Self::Init, _sender: relm4::ComponentSender) -> Self { Self { process: None, - systemconfig: None, + systemconfig: String::new(), flakeargs: None, syspkgs: params.syspkgs, userpkgs: params.userpkgs, @@ -69,7 +71,7 @@ impl Worker for UpdateAsyncHandler { fn update(&mut self, msg: Self::Input, sender: ComponentSender) { match msg { UpdateAsyncHandlerMsg::UpdateConfig(config) => { - self.systemconfig = config.systemconfig; + self.systemconfig = config.systemconfig.unwrap_or_default(); self.flakeargs = if let Some(flake) = config.flake { if let Some(flakearg) = config.flakearg { Some(format!("{}#{}", flake, flakearg)) @@ -84,46 +86,30 @@ impl Worker for UpdateAsyncHandler { self.syspkgs = syspkgs; self.userpkgs = userpkgs; } - // UpdateAsyncHandlerMsg::UpdateChannels => { - // let systemconfig = self.systemconfig.clone(); - // let flakeargs = self.flakeargs.clone(); - // let syspkgs = self.syspkgs.clone(); - // relm4::spawn(async move { - // let result = runcmd(NscCmd::Channel, systemconfig, flakeargs, syspkgs).await; - // match result { - // Ok(true) => { - // sender.output(UpdatePageMsg::DoneWorking); - // } - // _ => { - // warn!("UPDATE CHANNEL FAILED"); - // sender.output(UpdatePageMsg::FailedWorking); - // } - // } - // }); - // } - // UpdateAsyncHandlerMsg::UpdateChannelsAndSystem => { - // let systenconfig = self.systemconfig.clone(); - // let flakeargs = self.flakeargs.clone(); - // let syspkgs = self.syspkgs.clone(); - // relm4::spawn(async move { - // let result = runcmd(NscCmd::All, systenconfig, flakeargs, syspkgs).await; - // match result { - // Ok(true) => { - // sender.output(UpdatePageMsg::DoneWorking); - // } - // _ => { - // warn!("UPDATE CHANNEL AND SYSTEM FAILED"); - // sender.output(UpdatePageMsg::FailedWorking); - // } - // } - // }); - // } UpdateAsyncHandlerMsg::UpdateSystem => { let systemconfig = self.systemconfig.clone(); let flakeargs = self.flakeargs.clone(); let syspkgs = self.syspkgs.clone(); relm4::spawn(async move { - let result = runcmd(NscCmd::All, systemconfig, flakeargs, syspkgs).await; + let result = runcmd(NscCmd::All, systemconfig, flakeargs, syspkgs, None).await; + match result { + Ok(true) => { + sender.output(UpdatePageMsg::DoneWorking); + } + _ => { + warn!("UPDATE SYSTEM FAILED"); + sender.output(UpdatePageMsg::FailedWorking); + } + } + }); + } + UpdateAsyncHandlerMsg::UpdateSystemRemove(pkgs) => { + let systemconfig = self.systemconfig.clone(); + let flakeargs = self.flakeargs.clone(); + let syspkgs = self.syspkgs.clone(); + relm4::spawn(async move { + let result = + runcmd(NscCmd::All, systemconfig, flakeargs, syspkgs, Some(pkgs)).await; match result { Ok(true) => { sender.output(UpdatePageMsg::DoneWorking); @@ -141,8 +127,12 @@ impl Worker for UpdateAsyncHandler { let syspkgs = self.syspkgs.clone(); relm4::spawn(async move { let result = match syspkgs { - SystemPkgs::Legacy => runcmd(NscCmd::Rebuild, systemconfig, flakeargs, syspkgs).await, - SystemPkgs::Flake => runcmd(NscCmd::All, systemconfig, flakeargs, syspkgs).await, + SystemPkgs::Legacy => { + runcmd(NscCmd::Rebuild, systemconfig, flakeargs, syspkgs, None).await + } + SystemPkgs::Flake => { + runcmd(NscCmd::All, systemconfig, flakeargs, syspkgs, None).await + } SystemPkgs::None => Ok(true), }; match result { @@ -161,7 +151,25 @@ impl Worker for UpdateAsyncHandler { relm4::spawn(async move { let result = match userpkgs { UserPkgs::Env => updateenv().await, - UserPkgs::Profile => updateprofile().await, + UserPkgs::Profile => updateprofile(None).await, + }; + match result { + Ok(true) => { + sender.output(UpdatePageMsg::DoneWorking); + } + _ => { + warn!("UPDATE USER FAILED"); + sender.output(UpdatePageMsg::FailedWorking); + } + } + }); + } + UpdateAsyncHandlerMsg::UpdateUserPkgsRemove(pkgs) => { + let userpkgs = self.userpkgs.clone(); + relm4::spawn(async move { + let result = match userpkgs { + UserPkgs::Env => updateenv().await, + UserPkgs::Profile => updateprofile(Some(pkgs)).await, }; match result { Ok(true) => { @@ -180,12 +188,41 @@ impl Worker for UpdateAsyncHandler { let syspkgs = self.syspkgs.clone(); let userpkgs = self.userpkgs.clone(); relm4::spawn(async move { - let result = runcmd(NscCmd::All, systemconfig, flakeargs, syspkgs).await; + let result = runcmd(NscCmd::All, systemconfig, flakeargs, syspkgs, None).await; match result { Ok(true) => { match match userpkgs { UserPkgs::Env => updateenv().await, - UserPkgs::Profile => updateprofile().await, + UserPkgs::Profile => updateprofile(None).await, + } { + Ok(true) => { + sender.output(UpdatePageMsg::DoneWorking); + } + _ => { + warn!("UPDATE ALL FAILED"); + sender.output(UpdatePageMsg::FailedWorking); + } + } + } + _ => { + warn!("UPDATE ALL FAILED"); + sender.output(UpdatePageMsg::FailedWorking); + } + } + }); + } + UpdateAsyncHandlerMsg::UpdateAllRemove(userrmpkgs, sysrmpkgs) => { + let systemconfig = self.systemconfig.clone(); + let flakeargs = self.flakeargs.clone(); + let syspkgs = self.syspkgs.clone(); + let userpkgs = self.userpkgs.clone(); + relm4::spawn(async move { + let result = runcmd(NscCmd::All, systemconfig, flakeargs, syspkgs, Some(sysrmpkgs)).await; + match result { + Ok(true) => { + match match userpkgs { + UserPkgs::Env => updateenv().await, + UserPkgs::Profile => updateprofile(Some(userrmpkgs)).await, } { Ok(true) => { sender.output(UpdatePageMsg::DoneWorking); @@ -209,10 +246,12 @@ impl Worker for UpdateAsyncHandler { async fn runcmd( cmd: NscCmd, - _systemconfig: Option, + systemconfig: String, flakeargs: Option, syspkgs: SystemPkgs, + rmpkgs: Option>, ) -> Result { + let f = fs::read_to_string(&systemconfig)?; let exe = match std::env::current_exe() { Ok(mut e) => { e.pop(); // root/bin @@ -231,7 +270,12 @@ async fn runcmd( }; let flakepathsplit = flakeargs.clone().unwrap_or_default().to_string(); - let flakepath = flakepathsplit.split('#').collect::>().first().cloned().unwrap_or_default(); + let flakepath = flakepathsplit + .split('#') + .collect::>() + .first() + .cloned() + .unwrap_or_default(); let rebuildargs = if let Some(x) = flakeargs { let mut v = vec![String::from("--flake")]; @@ -260,27 +304,92 @@ async fn runcmd( .stderr(Stdio::piped()) .spawn()?, NscCmd::All => match syspkgs { - SystemPkgs::Legacy => tokio::process::Command::new("pkexec") - .arg(&exe) - .arg("channel") - .arg("--rebuild") - .arg("--") - .arg("switch") - .args(&rebuildargs) - .stderr(Stdio::piped()) - .spawn()?, - SystemPkgs::Flake => tokio::process::Command::new("pkexec") - .arg(&exe) - .arg("flake") - .arg("--rebuild") - .arg("--flakepath") - .arg(flakepath) - .arg("--") - .arg("switch") - .arg("--impure") - .args(&rebuildargs) - .stderr(Stdio::piped()) - .spawn()?, + SystemPkgs::Legacy => { + if let Some(rmpkgs) = rmpkgs { + let newconfig = + match nix_editor::write::rmarr(&f, "environment.systemPackages", rmpkgs) { + Ok(x) => x, + Err(_) => { + return Err(anyhow!("Failed to write configuration.nix")); + } + }; + let mut cmd = tokio::process::Command::new("pkexec") + .arg(&exe) + .arg("channel") + .arg("--rebuild") + .arg("--update") + .arg("--output") + .arg(&systemconfig) + .arg("--") + .arg("switch") + .args(&rebuildargs) + .stderr(Stdio::piped()) + .stdin(Stdio::piped()) + .spawn()?; + cmd.stdin + .take() + .unwrap() + .write_all(newconfig.as_bytes()) + .await?; + cmd + } else { + tokio::process::Command::new("pkexec") + .arg(&exe) + .arg("channel") + .arg("--rebuild") + .arg("--") + .arg("switch") + .args(&rebuildargs) + .stderr(Stdio::piped()) + .spawn()? + } + } + SystemPkgs::Flake => { + if let Some(rmpkgs) = rmpkgs { + let newconfig = + match nix_editor::write::rmarr(&f, "environment.systemPackages", rmpkgs) { + Ok(x) => x, + Err(_) => { + return Err(anyhow!("Failed to write configuration.nix")); + } + }; + let mut cmd = tokio::process::Command::new("pkexec") + .arg(&exe) + .arg("flake") + .arg("--rebuild") + .arg("--flakepath") + .arg(&flakepath) + .arg("--update") + .arg("--output") + .arg(&systemconfig) + .arg("--") + .arg("switch") + .arg("--impure") + .args(&rebuildargs) + .stderr(Stdio::piped()) + .stdin(Stdio::piped()) + .spawn()?; + cmd.stdin + .take() + .unwrap() + .write_all(newconfig.as_bytes()) + .await?; + cmd + } else { + tokio::process::Command::new("pkexec") + .arg(&exe) + .arg("flake") + .arg("--rebuild") + .arg("--flakepath") + .arg(&flakepath) + .arg("--") + .arg("switch") + .arg("--impure") + .args(&rebuildargs) + .stderr(Stdio::piped()) + .spawn()? + } + } SystemPkgs::None => return Ok(true), }, }; @@ -321,7 +430,38 @@ async fn updateenv() -> Result { } } -async fn updateprofile() -> Result { +async fn updateprofile(rmpkgs: Option>) -> Result { + if let Some(rmpkgs) = rmpkgs { + if !rmpkgs.is_empty() { + let mut cmd = tokio::process::Command::new("nix") + .arg("profile") + .arg("remove") + .args( + &rmpkgs + .iter() + .map(|x| format!( + "legacyPackages.x86_64-linux.{}", + x + )) + .collect::>() + ) + // Allow updating potential unfree packages + .arg("--impure") + .stderr(Stdio::piped()) + .spawn()?; + + let stderr = cmd.stderr.take().unwrap(); + let reader = tokio::io::BufReader::new(stderr); + + let mut lines = reader.lines(); + while let Ok(Some(line)) = lines.next_line().await { + REBUILD_BROKER.send(RebuildMsg::UpdateText(line.to_string())); + trace!("CAUGHT NIX PROFILE LINE: {}", line); + } + cmd.wait().await?; + } + } + let mut cmd = tokio::process::Command::new("nix") .arg("profile") .arg("upgrade") @@ -344,4 +484,4 @@ async fn updateprofile() -> Result { } else { Ok(false) } -} \ No newline at end of file +} diff --git a/src/ui/window.rs b/src/ui/window.rs index faa000f..9a8e95f 100644 --- a/src/ui/window.rs +++ b/src/ui/window.rs @@ -4,7 +4,7 @@ use crate::{ config::{editconfig, getconfig}, packages::{AppData, LicenseEnum, PkgMaintainer, Platform}, }, - ui::{installedpage::InstalledItem, pkgpage::PkgPageInit, welcome::WelcomeMsg, rebuild::RebuildMsg}, + ui::{installedpage::InstalledItem, pkgpage::PkgPageInit, welcome::WelcomeMsg, rebuild::RebuildMsg, updatepage::UNAVAILABLE_BROKER, unavailabledialog::UnavailableDialogMsg}, APPINFO, }; use adw::prelude::*; @@ -30,9 +30,9 @@ use super::{ pkgtile::PkgTile, preferencespage::{PreferencesPageModel, PreferencesPageMsg}, searchpage::{SearchItem, SearchPageModel, SearchPageMsg}, - updatepage::{UpdateItem, UpdatePageInit, UpdatePageModel, UpdatePageMsg}, + updatepage::{UpdateItem, UpdatePageInit, UpdatePageModel, UpdatePageMsg, UpdateType}, welcome::WelcomeModel, - windowloading::{LoadErrorModel, LoadErrorMsg, WindowAsyncHandler, WindowAsyncHandlerMsg}, rebuild::RebuildModel, + windowloading::{LoadErrorModel, LoadErrorMsg, WindowAsyncHandler, WindowAsyncHandlerMsg}, rebuild::RebuildModel, unavailabledialog::UnavailableItemModel, }; pub static REBUILD_BROKER: MessageBroker = MessageBroker::new(); @@ -152,6 +152,7 @@ pub enum AppMsg { LoadCategory(PkgCategory), UpdateRecPkgs(Vec), SetDarkMode(bool), + GetUnavailableItems(HashMap, HashMap, UpdateType), } #[derive(Debug, Clone, PartialEq, Eq)] @@ -1825,6 +1826,133 @@ FROM pkgs JOIN meta ON (pkgs.attribute = meta.attribute) WHERE pkgs.attribute = let scheme = if dark { "Adwaita-dark" } else { "Adwaita" }; self.rebuild.emit(RebuildMsg::SetScheme(scheme.to_string())); } + AppMsg::GetUnavailableItems(userpkgs, syspkgs, updatetype) => { + info!("AppMsg::GetUnavailableItems"); + let appdata: HashMap = self + .appdata + .iter() + .filter_map(|(k, v)| { + if syspkgs.contains_key(k) || userpkgs.contains_key(k) { + Some((k.to_string(), v.clone())) + } else { + None + } + }) + .collect(); + let poolref = self.pkgdb.clone(); + relm4::spawn(async move { + let mut unavailableuser = vec![]; + let mut unavailablesys = vec![]; + if let Ok(pool) = &SqlitePool::connect(&format!("sqlite://{}", poolref)).await { + let mut sortuserpkgs = userpkgs.into_iter().collect::>(); + sortuserpkgs.sort(); + for (pkg, msg) in sortuserpkgs { + if let Some(data) = appdata.get(&pkg) { + let pname: Result<(String,), sqlx::Error> = + sqlx::query_as("SELECT pname FROM pkgs WHERE attribute = $1") + .bind(&pkg) + .fetch_one(pool) + .await; + if let Ok(pname) = pname { + unavailableuser.push(UnavailableItemModel { + pkg: pkg.to_string(), + name: if let Some(name) = &data.name { + name.get("C").unwrap_or(&pname.0).to_string() + } else { + pname.0.to_string() + }, + pname: pname.0.to_string(), + icon: data + .icon + .as_ref() + .and_then(|x| x.cached.as_ref()) + .map(|x| x[0].name.clone()), + message: msg + }) + } else { + unavailableuser.push(UnavailableItemModel { + pkg: pkg.to_string(), + name: if let Some(name) = &data.name { + name.get("C").unwrap_or(&pkg).to_string() + } else { + pkg.to_string() + }, + pname: String::new(), + icon: data + .icon + .as_ref() + .and_then(|x| x.cached.as_ref()) + .map(|x| x[0].name.clone()), + message: msg + }) + } + } else { + unavailableuser.push(UnavailableItemModel { + pkg: pkg.to_string(), + name: pkg.to_string(), + pname: String::new(), + icon: None, + message: msg + }) + } + } + let mut sortsyspkgs = syspkgs.into_iter().collect::>(); + sortsyspkgs.sort(); + for (pkg, msg) in sortsyspkgs { + if let Some(data) = appdata.get(&pkg) { + let pname: Result<(String,), sqlx::Error> = + sqlx::query_as("SELECT pname FROM pkgs WHERE attribute = $1") + .bind(&pkg) + .fetch_one(pool) + .await; + if let Ok(pname) = pname { + unavailablesys.push(UnavailableItemModel { + pkg: pkg.to_string(), + name: if let Some(name) = &data.name { + name.get("C").unwrap_or(&pname.0).to_string() + } else { + pname.0.to_string() + }, + pname: pname.0.to_string(), + icon: data + .icon + .as_ref() + .and_then(|x| x.cached.as_ref()) + .map(|x| x[0].name.clone()), + message: msg + }) + } else { + unavailablesys.push(UnavailableItemModel { + pkg: pkg.to_string(), + name: if let Some(name) = &data.name { + name.get("C").unwrap_or(&pkg).to_string() + } else { + pkg.to_string() + }, + pname: String::new(), + icon: data + .icon + .as_ref() + .and_then(|x| x.cached.as_ref()) + .map(|x| x[0].name.clone()), + message: msg + }) + } + } else { + unavailablesys.push(UnavailableItemModel { + pkg: pkg.to_string(), + name: pkg.to_string(), + pname: String::new(), + icon: None, + message: msg + }) + } + } + + } + UNAVAILABLE_BROKER.send(UnavailableDialogMsg::Show(unavailableuser, unavailablesys, updatetype)); + }); + } } }