diff --git a/src/parse/cache.rs b/src/parse/cache.rs index 0a8a320..4cfb8e4 100644 --- a/src/parse/cache.rs +++ b/src/parse/cache.rs @@ -36,7 +36,14 @@ pub fn checkcache(syspkgs: SystemPkgs, userpkgs: UserPkgs, config: NscConfig) -> SystemPkgs::Flake => { setupflakepkgscache(config)?; } + SystemPkgs::None => {} } + + if userpkgs == UserPkgs::Env && syspkgs != SystemPkgs::Legacy { + setupupdatecache()?; + setupnewestver()?; + } + if userpkgs == UserPkgs::Profile { setupprofilepkgscache()?; } @@ -318,7 +325,12 @@ fn setupprofilepkgscache() -> Result<(), Box> { // nix-instantiate --eval -E '(builtins.getFlake "/home/user/nix").inputs.nixpkgs.outPath' // nix-env -f /nix/store/sjmq1gphj1arbzf4aqqnygd9pf4hkfkf-source -qa --json > packages.json fn setupupdatecache() -> Result<(), Box> { - let dlver = fs::read_to_string("/run/current-system/nixos-version")?; + let output = Command::new("nix-instantiate") + .arg("--eval") + .arg("-E") + .arg("with import {}; pkgs.lib.version") + .output()?; + let dlver = String::from_utf8(output.stdout)?.replace("\"", ""); let mut relver = dlver.split('.').collect::>().join(".")[0..5].to_string(); @@ -373,8 +385,12 @@ fn setupupdatecache() -> Result<(), Box> { } fn setupnewestver() -> Result<(), Box> { - let version = fs::read_to_string("/run/current-system/nixos-version")?; - + let output = Command::new("nix-instantiate") + .arg("--eval") + .arg("-E") + .arg("with import {}; pkgs.lib.version") + .output()?; + let version = String::from_utf8(output.stdout)?.replace("\"", ""); let mut relver = version.split('.').collect::>().join(".")[0..5].to_string(); if version.len() >= 8 && &version[5..8] == "pre" { diff --git a/src/parse/config.rs b/src/parse/config.rs index 8582e82..87ae87a 100644 --- a/src/parse/config.rs +++ b/src/parse/config.rs @@ -10,8 +10,9 @@ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)] pub struct NscConfig { - pub systemconfig: String, + pub systemconfig: Option, pub flake: Option, + pub flakearg: Option, } pub fn getconfig() -> Option { diff --git a/src/parse/packages.rs b/src/parse/packages.rs index 02f6d29..c926334 100644 --- a/src/parse/packages.rs +++ b/src/parse/packages.rs @@ -1,6 +1,7 @@ use flate2::bufread::GzDecoder; use ijson::IString; use serde::{Deserialize, Serialize}; +use std::fs; use std::io::Read; use std::{self, fs::File, collections::HashMap, error::Error, env, io::BufReader}; use log::*; @@ -179,9 +180,12 @@ pub async fn readpkgs() -> Result, Box>(); files.remove(0); for f in files { - let appstream: AppData = serde_yaml::from_str(f)?; - if let Some(p) = pkgs.get_mut(&appstream.package.to_string()) { - p.appdata = Some(appstream); + if let Ok(appstream) = serde_yaml::from_str::(f) { + if let Some(p) = pkgs.get_mut(&appstream.package.to_string()) { + p.appdata = Some(appstream); + } + } else { + warn!("Failed to parse some appstream data"); } } Ok(pkgs) @@ -190,6 +194,11 @@ pub async fn readpkgs() -> Result, Box Result, Box> { let cachedir = format!("{}/.cache/nix-software-center/", env::var("HOME")?); let cachefile = format!("{}/syspackages.json", cachedir); + if let Ok(f) = fs::read_to_string(&cachefile) { + if f.trim().is_empty() { + return Ok(HashMap::new()); + } + } let file = File::open(cachefile)?; let reader = BufReader::new(file); let newpkgs: HashMap = simd_json::serde::from_reader(reader)?; @@ -199,6 +208,11 @@ pub fn readlegacysyspkgs() -> Result, Box Result, Box> { let cachedir = format!("{}/.cache/nix-software-center/", env::var("HOME")?); let cachefile = format!("{}/syspackages.json", cachedir); + if let Ok(f) = fs::read_to_string(&cachefile) { + if f.trim().is_empty() { + return Ok(HashMap::new()); + } + } let file = File::open(cachefile)?; let reader = BufReader::new(file); let newpkgs: HashMap = simd_json::serde::from_reader(reader)?; @@ -209,6 +223,11 @@ pub fn readflakesyspkgs() -> Result, Box Result, Box> { let cachedir = format!("{}/.cache/nix-software-center/", env::var("HOME")?); let cachefile = format!("{}/profilepackages.json", cachedir); + if let Ok(f) = fs::read_to_string(&cachefile) { + if f.trim().is_empty() { + return Ok(HashMap::new()); + } + } let file = File::open(cachefile)?; let reader = BufReader::new(file); let profilepkgs: HashMap = simd_json::serde::from_reader(reader)?; diff --git a/src/ui/installedpage.rs b/src/ui/installedpage.rs index 594b2b3..edbdaa0 100644 --- a/src/ui/installedpage.rs +++ b/src/ui/installedpage.rs @@ -13,12 +13,14 @@ pub struct InstalledPageModel { #[tracker::no_eq] installedsystemlist: FactoryVecDeque, userpkgtype: UserPkgs, + systempkgtype: SystemPkgs, updatetracker: u8, } #[derive(Debug)] pub enum InstalledPageMsg { Update(Vec, Vec), + UpdatePkgTypes(SystemPkgs, UserPkgs), OpenRow(usize, InstallType), Remove(InstalledItem), UnsetBusy(WorkPkg), @@ -26,7 +28,7 @@ pub enum InstalledPageMsg { #[relm4::component(pub)] impl SimpleComponent for InstalledPageModel { - type InitParams = UserPkgs; + type InitParams = (SystemPkgs, UserPkgs); type Input = InstalledPageMsg; type Output = AppMsg; type Widgets = InstalledPageWidgets; @@ -43,6 +45,8 @@ impl SimpleComponent for InstalledPageModel { set_margin_all: 15, set_spacing: 15, gtk::Label { + #[watch] + set_visible: !model.installeduserlist.is_empty(), set_halign: gtk::Align::Start, add_css_class: "title-4", set_label: match model.userpkgtype { @@ -52,6 +56,8 @@ impl SimpleComponent for InstalledPageModel { }, #[local_ref] installeduserlist -> gtk::ListBox { + #[watch] + set_visible: !model.installeduserlist.is_empty(), set_valign: gtk::Align::Start, add_css_class: "boxed-list", set_selection_mode: gtk::SelectionMode::None, @@ -62,12 +68,16 @@ impl SimpleComponent for InstalledPageModel { } }, gtk::Label { + #[watch] + set_visible: !model.installedsystemlist.is_empty(), set_halign: gtk::Align::Start, add_css_class: "title-4", set_label: "System (configuration.nix)", }, #[local_ref] installedsystemlist -> gtk::ListBox { + #[watch] + set_visible: !model.installedsystemlist.is_empty(), set_valign: gtk::Align::Start, add_css_class: "boxed-list", set_selection_mode: gtk::SelectionMode::None, @@ -83,7 +93,7 @@ impl SimpleComponent for InstalledPageModel { } fn init( - userpkgtype: Self::InitParams, + (systempkgtype, userpkgtype): Self::InitParams, root: &Self::Root, sender: ComponentSender, ) -> ComponentParts { @@ -92,6 +102,7 @@ impl SimpleComponent for InstalledPageModel { installedsystemlist: FactoryVecDeque::new(gtk::ListBox::new(), &sender.input), updatetracker: 0, userpkgtype, + systempkgtype, tracker: 0 }; @@ -119,6 +130,10 @@ impl SimpleComponent for InstalledPageModel { installedsystemlist_guard.push_back(installedsystem); } } + InstalledPageMsg::UpdatePkgTypes(systempkgtype, userpkgtype) => { + self.systempkgtype = systempkgtype; + self.userpkgtype = userpkgtype; + } InstalledPageMsg::OpenRow(row, pkgtype) => { match pkgtype { InstallType::User => { diff --git a/src/ui/installworker.rs b/src/ui/installworker.rs index efeb963..ac7b018 100644 --- a/src/ui/installworker.rs +++ b/src/ui/installworker.rs @@ -1,14 +1,14 @@ use crate::parse::config::NscConfig; use super::pkgpage::{InstallType, PkgAction, PkgMsg, WorkPkg}; -use super::window::{UserPkgs, SystemPkgs}; +use super::window::{SystemPkgs, UserPkgs}; +use log::*; use relm4::*; use std::error::Error; use std::path::Path; use std::process::Stdio; use std::{fs, io}; use tokio::io::{AsyncBufReadExt, AsyncWriteExt}; -use log::*; #[tracker::track] #[derive(Debug)] @@ -16,7 +16,7 @@ pub struct InstallAsyncHandler { #[tracker::no_eq] process: Option>, work: Option, - systemconfig: String, + systemconfig: Option, flakeargs: Option, pid: Option, syspkgs: SystemPkgs, @@ -26,6 +26,7 @@ pub struct InstallAsyncHandler { #[derive(Debug)] pub enum InstallAsyncHandlerMsg { SetConfig(NscConfig), + SetPkgTypes(SystemPkgs, UserPkgs), Process(WorkPkg), CancelProcess, SetPid(Option), @@ -46,7 +47,7 @@ impl Worker for InstallAsyncHandler { Self { process: None, work: None, - systemconfig: String::new(), + systemconfig: None, flakeargs: None, pid: None, syspkgs: params.syspkgs, @@ -60,8 +61,21 @@ impl Worker for InstallAsyncHandler { match msg { InstallAsyncHandlerMsg::SetConfig(config) => { self.systemconfig = config.systemconfig; - self.flakeargs = config.flake; + self.flakeargs = if let Some(flake) = config.flake { + if let Some(flakearg) = config.flakearg { + Some(format!("{}#{}", flake, flakearg)) + } else { + Some(flake) + } + } else { + None + } } + InstallAsyncHandlerMsg::SetPkgTypes(syspkgs, userpkgs) => { + self.syspkgs = syspkgs; + self.userpkgs = userpkgs; + } + InstallAsyncHandlerMsg::Process(work) => { if work.block { return; @@ -69,240 +83,246 @@ impl Worker for InstallAsyncHandler { let systemconfig = self.systemconfig.clone(); let rebuildargs = self.flakeargs.clone(); match work.pkgtype { - InstallType::User => { - match work.action { - PkgAction::Install => { - info!("Installing user package: {}", work.pkg); - match self.userpkgs { - UserPkgs::Env => { - self.process = Some(relm4::spawn(async move { - let mut p = tokio::process::Command::new("nix-env") - .arg("-iA") - .arg(format!("nixos.{}", work.pkg)) - .kill_on_drop(true) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .expect("Failed to run nix-env"); - - let stderr = p.stderr.take().unwrap(); - let reader = tokio::io::BufReader::new(stderr); - - let mut lines = reader.lines(); - while let Ok(Some(line)) = lines.next_line().await { - trace!("CAUGHT LINE: {}", line); - } - - match p.wait().await { - Ok(o) => { - if o.success() { - info!( - "Removed user package: {} success", - work.pkg - ); - sender.output(PkgMsg::FinishedProcess(work)) - } else { - warn!( - "Removed user package: {} failed", - work.pkg - ); - sender.output(PkgMsg::FailedProcess(work)); - } - } - Err(e) => { - warn!("Error removing user package: {}", e); + InstallType::User => match work.action { + PkgAction::Install => { + info!("Installing user package: {}", work.pkg); + match self.userpkgs { + UserPkgs::Env => { + self.process = Some(relm4::spawn(async move { + let mut p = tokio::process::Command::new("nix-env") + .arg("-iA") + .arg(format!("nixos.{}", work.pkg)) + .kill_on_drop(true) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .expect("Failed to run nix-env"); + + let stderr = p.stderr.take().unwrap(); + let reader = tokio::io::BufReader::new(stderr); + + let mut lines = reader.lines(); + while let Ok(Some(line)) = lines.next_line().await { + trace!("CAUGHT LINE: {}", line); + } + + match p.wait().await { + Ok(o) => { + if o.success() { + info!( + "Removed user package: {} success", + work.pkg + ); + sender.output(PkgMsg::FinishedProcess(work)) + } else { + warn!( + "Removed user package: {} failed", + work.pkg + ); sender.output(PkgMsg::FailedProcess(work)); } } - })); - } - UserPkgs::Profile => { - self.process = Some(relm4::spawn(async move { - let mut p = tokio::process::Command::new("nix") - .arg("profile") - .arg("install") - .arg(format!("nixpkgs#{}", work.pkg)) - .kill_on_drop(true) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .expect("Failed to run nix profile"); - - let stderr = p.stderr.take().unwrap(); - let reader = tokio::io::BufReader::new(stderr); - - let mut lines = reader.lines(); - while let Ok(Some(line)) = lines.next_line().await { - trace!("CAUGHT LINE: {}", line); + Err(e) => { + warn!("Error removing user package: {}", e); + sender.output(PkgMsg::FailedProcess(work)); } - - match p.wait().await { - Ok(o) => { - if o.success() { - info!( - "Removed user package: {} success", - work.pkg - ); - sender.output(PkgMsg::FinishedProcess(work)) - } else { - warn!( - "Removed user package: {} failed", - work.pkg - ); - sender.output(PkgMsg::FailedProcess(work)); - } - } - Err(e) => { - warn!("Error removing user package: {}", e); + } + })); + } + UserPkgs::Profile => { + self.process = Some(relm4::spawn(async move { + let mut p = tokio::process::Command::new("nix") + .arg("profile") + .arg("install") + .arg(format!("nixpkgs#{}", work.pkg)) + .kill_on_drop(true) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .expect("Failed to run nix profile"); + + let stderr = p.stderr.take().unwrap(); + let reader = tokio::io::BufReader::new(stderr); + + let mut lines = reader.lines(); + while let Ok(Some(line)) = lines.next_line().await { + trace!("CAUGHT LINE: {}", line); + } + + match p.wait().await { + Ok(o) => { + if o.success() { + info!( + "Removed user package: {} success", + work.pkg + ); + sender.output(PkgMsg::FinishedProcess(work)) + } else { + warn!( + "Removed user package: {} failed", + work.pkg + ); sender.output(PkgMsg::FailedProcess(work)); } } - })); - } + Err(e) => { + warn!("Error removing user package: {}", e); + sender.output(PkgMsg::FailedProcess(work)); + } + } + })); } } - PkgAction::Remove => { - info!("Removing user package: {}", work.pkg); - match self.userpkgs { - UserPkgs::Env => { - self.process = Some(relm4::spawn(async move { - let mut p = tokio::process::Command::new("nix-env") - .arg("-e") - .arg(&work.pname) - .kill_on_drop(true) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .expect("Failed to run nix-env"); - let stderr = p.stderr.take().unwrap(); - let reader = tokio::io::BufReader::new(stderr); - - let mut lines = reader.lines(); - while let Ok(Some(line)) = lines.next_line().await { - trace!("CAUGHT LINE: {}", line); - } - match p.wait().await { - Ok(o) => { - if o.success() { - info!( - "Removed user package: {} success", - work.pkg - ); - sender.output(PkgMsg::FinishedProcess(work)) - } else { - warn!( - "Removed user package: {} failed", - work.pkg - ); - sender.output(PkgMsg::FailedProcess(work)); - } - } - Err(e) => { - warn!("Error removing user package: {}", e); - sender.output(PkgMsg::FailedProcess(work)); - } - } - })); - } - UserPkgs::Profile => { - self.process = Some(relm4::spawn(async move { - let mut p = tokio::process::Command::new("nix") - .arg("profile") - .arg("remove") - .arg(&format!("legacyPackages.x86_64-linux.{}", work.pkg)) - .kill_on_drop(true) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .expect("Failed to run nix profile"); - let stderr = p.stderr.take().unwrap(); - let reader = tokio::io::BufReader::new(stderr); - - let mut lines = reader.lines(); - while let Ok(Some(line)) = lines.next_line().await { - trace!("CAUGHT LINE: {}", line); - } - match p.wait().await { - Ok(o) => { - if o.success() { - info!( - "Removed user package: {} success", - work.pkg - ); - sender.output(PkgMsg::FinishedProcess(work)) - } else { - warn!( - "Removed user package: {} failed", - work.pkg - ); - sender.output(PkgMsg::FailedProcess(work)); - } } + } + PkgAction::Remove => { + info!("Removing user package: {}", work.pkg); + match self.userpkgs { + UserPkgs::Env => { + self.process = Some(relm4::spawn(async move { + let mut p = tokio::process::Command::new("nix-env") + .arg("-e") + .arg(&work.pname) + .kill_on_drop(true) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .expect("Failed to run nix-env"); + let stderr = p.stderr.take().unwrap(); + let reader = tokio::io::BufReader::new(stderr); - Err(e) => { - warn!("Error removing user package: {}", e); + let mut lines = reader.lines(); + while let Ok(Some(line)) = lines.next_line().await { + trace!("CAUGHT LINE: {}", line); + } + match p.wait().await { + Ok(o) => { + if o.success() { + info!( + "Removed user package: {} success", + work.pkg + ); + sender.output(PkgMsg::FinishedProcess(work)) + } else { + warn!( + "Removed user package: {} failed", + work.pkg + ); sender.output(PkgMsg::FailedProcess(work)); } } - })); - } + Err(e) => { + warn!("Error removing user package: {}", e); + sender.output(PkgMsg::FailedProcess(work)); + } + } + })); + } + UserPkgs::Profile => { + self.process = Some(relm4::spawn(async move { + let mut p = tokio::process::Command::new("nix") + .arg("profile") + .arg("remove") + .arg(&format!( + "legacyPackages.x86_64-linux.{}", + work.pkg + )) + .kill_on_drop(true) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .expect("Failed to run nix profile"); + let stderr = p.stderr.take().unwrap(); + let reader = tokio::io::BufReader::new(stderr); + + let mut lines = reader.lines(); + while let Ok(Some(line)) = lines.next_line().await { + trace!("CAUGHT LINE: {}", line); + } + match p.wait().await { + Ok(o) => { + if o.success() { + info!( + "Removed user package: {} success", + work.pkg + ); + sender.output(PkgMsg::FinishedProcess(work)) + } else { + warn!( + "Removed user package: {} failed", + work.pkg + ); + sender.output(PkgMsg::FailedProcess(work)); + } + } + + Err(e) => { + warn!("Error removing user package: {}", e); + sender.output(PkgMsg::FailedProcess(work)); + } + } + })); + } + } + } + }, + InstallType::System => { + if let Some(systemconfig) = systemconfig { + match work.action { + PkgAction::Install => { + info!("Installing system package: {}", work.pkg); + self.process = Some(relm4::spawn(async move { + match installsys( + work.pkg.to_string(), + work.action.clone(), + systemconfig, + rebuildargs, + sender.clone(), + ) + .await + { + Ok(b) => { + if b { + sender.output(PkgMsg::FinishedProcess(work)) + } else { + sender.output(PkgMsg::FailedProcess(work)) + } + } + Err(e) => { + sender.output(PkgMsg::FailedProcess(work)); + warn!("Error installing system package: {}", e); + } + } + })); + } + PkgAction::Remove => { + info!("Removing system package: {}", work.pkg); + self.process = Some(relm4::spawn(async move { + match installsys( + work.pkg.to_string(), + work.action.clone(), + systemconfig, + rebuildargs, + sender.clone(), + ) + .await + { + Ok(b) => { + if b { + sender.output(PkgMsg::FinishedProcess(work)) + } else { + sender.output(PkgMsg::FailedProcess(work)) + } + } + Err(e) => { + sender.output(PkgMsg::FailedProcess(work)); + warn!("Error removing system package: {}", e); + } + } + })); } } } } - InstallType::System => match work.action { - PkgAction::Install => { - info!("Installing system package: {}", work.pkg); - self.process = Some(relm4::spawn(async move { - match installsys( - work.pkg.to_string(), - work.action.clone(), - systemconfig, - rebuildargs, - sender.clone(), - ) - .await - { - Ok(b) => { - if b { - sender.output(PkgMsg::FinishedProcess(work)) - } else { - sender.output(PkgMsg::FailedProcess(work)) - } - } - Err(e) => { - sender.output(PkgMsg::FailedProcess(work)); - warn!("Error installing system package: {}", e); - } - } - })); - } - PkgAction::Remove => { - info!("Removing system package: {}", work.pkg); - self.process = Some(relm4::spawn(async move { - match installsys( - work.pkg.to_string(), - work.action.clone(), - systemconfig, - rebuildargs, - sender.clone(), - ) - .await - { - Ok(b) => { - if b { - sender.output(PkgMsg::FinishedProcess(work)) - } else { - sender.output(PkgMsg::FailedProcess(work)) - } - } - Err(e) => { - sender.output(PkgMsg::FailedProcess(work)); - warn!("Error removing system package: {}", e); - } - } - })); - } - }, } } InstallAsyncHandlerMsg::CancelProcess => { @@ -404,7 +424,7 @@ async fn installsys( .stderr(Stdio::piped()) .stdin(Stdio::piped()) .spawn()?; - + // sender.input(InstallAsyncHandlerMsg::SetPid(cmd.id())); cmd.stdin.take().unwrap().write_all(out.as_bytes()).await?; diff --git a/src/ui/pkgpage.rs b/src/ui/pkgpage.rs index 7e9df3b..11d443a 100644 --- a/src/ui/pkgpage.rs +++ b/src/ui/pkgpage.rs @@ -136,6 +136,7 @@ pub struct PkgInitModel { #[derive(Debug)] pub enum PkgMsg { UpdateConfig(NscConfig), + UpdatePkgTypes(SystemPkgs, UserPkgs), Open(Box), LoadScreenshot(String, usize, String), SetError(String, usize), @@ -148,10 +149,6 @@ pub enum PkgMsg { RemoveSystem, Cancel, CancelFinished, - // FinishedInstallUser(String, String), - // FailedInstallUser(String, String), - // FinishedRemoveUser(String, String), - // FailedRemoveUser(String, String), FinishedProcess(WorkPkg), FailedProcess(WorkPkg), Launch, @@ -203,6 +200,9 @@ impl Component for PkgModel { set_label: &model.name }, pack_end = >k::MenuButton { + #[watch] + set_visible: model.syspkgtype != SystemPkgs::None, + #[watch] set_label: match model.userpkgtype { UserPkgs::Env => { @@ -1044,6 +1044,11 @@ impl Component for PkgModel { self.config = config.clone(); self.installworker.emit(InstallAsyncHandlerMsg::SetConfig(config)); } + PkgMsg::UpdatePkgTypes(syspkgs, userpkgs) => { + self.syspkgtype = syspkgs.clone(); + self.userpkgtype = userpkgs.clone(); + self.installworker.emit(InstallAsyncHandlerMsg::SetPkgTypes(syspkgs, userpkgs)); + } PkgMsg::Open(pkgmodel) => { self.set_pkg(pkgmodel.pkg); self.set_name(pkgmodel.name); diff --git a/src/ui/preferencespage.rs b/src/ui/preferencespage.rs index 17851dd..c78215a 100644 --- a/src/ui/preferencespage.rs +++ b/src/ui/preferencespage.rs @@ -1,5 +1,7 @@ use std::path::PathBuf; +use crate::parse::config::NscConfig; + use super::window::AppMsg; use adw::prelude::*; use relm4::*; @@ -9,8 +11,9 @@ use relm4_components::open_dialog::*; #[derive(Debug)] pub struct PreferencesPageModel { hidden: bool, - configpath: PathBuf, - flake: Option<(PathBuf, String)>, + configpath: Option, + flake: Option, + flakearg: Option, #[tracker::no_eq] open_dialog: Controller, #[tracker::no_eq] @@ -19,13 +22,12 @@ pub struct PreferencesPageModel { #[derive(Debug)] pub enum PreferencesPageMsg { - Show(PathBuf, Option<(PathBuf, String)>), + Show(NscConfig), Open, OpenFlake, - SetConfigPath(PathBuf), - SetFlake(Option<(PathBuf, String)>), - SetFlakePath(PathBuf), - SetFlakeArg(String), + SetConfigPath(Option), + SetFlakePath(Option), + SetFlakeArg(Option), ModifyFlake, Ignore, } @@ -64,7 +66,7 @@ impl SimpleComponent for PreferencesPageModel { gtk::Label { #[watch] set_label: { - let x = model.configpath.file_name().unwrap_or_default().to_str().unwrap_or_default(); + let x = if let Some(configpath) = &model.configpath { configpath.file_name().unwrap_or_default().to_str().unwrap_or_default() } else { "(None)" }; if x.is_empty() { "(None)" } else { @@ -77,13 +79,12 @@ impl SimpleComponent for PreferencesPageModel { sender.input(PreferencesPageMsg::Open); } }, - // gtk::Button { - // add_css_class: "flat", - // set_icon_name: "view-refresh-symbolic", - // connect_clicked[sender] => move |_| { - // sender.input(PreferencesPageMsg::SetConfigPath(PathBuf::from("/etc/nixos/configuration.nix"))); - // } - // } + gtk::Button { + set_icon_name: "user-trash-symbolic", + connect_clicked[sender] => move |_| { + sender.input(PreferencesPageMsg::SetConfigPath(None)); + } + } } }, add = &adw::ActionRow { @@ -92,9 +93,10 @@ impl SimpleComponent for PreferencesPageModel { set_valign: gtk::Align::Center, connect_state_set[sender] => move |_, b| { if b { - sender.input(PreferencesPageMsg::SetFlake(Some((PathBuf::new(), String::default())))); + sender.input(PreferencesPageMsg::SetFlakePath(Some(PathBuf::new()))); } else { - sender.input(PreferencesPageMsg::SetFlake(None)); + sender.input(PreferencesPageMsg::SetFlakePath(None)); + sender.input(PreferencesPageMsg::SetFlakeArg(None)); } gtk::Inhibit(false) } @switched, @@ -122,7 +124,7 @@ impl SimpleComponent for PreferencesPageModel { gtk::Label { #[watch] set_label: { - let x = if let Some((f, _)) = &model.flake { + let x = if let Some(f) = &model.flake { f.file_name().unwrap_or_default().to_str().unwrap_or_default() } else { "" @@ -153,11 +155,17 @@ impl SimpleComponent for PreferencesPageModel { set_visible: model.flake.is_some(), set_title: "Flake arguments (--flake path/to/flake.nix#)", connect_changed[sender] => move |x| { - sender.input(PreferencesPageMsg::SetFlakeArg(x.text().to_string())); + sender.input(PreferencesPageMsg::SetFlakeArg({ + let text = x.text().to_string(); + if text.is_empty() { + None + } else { + Some(text) + }})); } @flakeentry, #[track(model.changed(PreferencesPageModel::flake()))] #[block_signal(flakeentry)] - set_text: &model.flake.as_ref().map(|(_, a)| a.to_string()).unwrap_or_default() + set_text: &model.flakearg.as_ref().unwrap_or(&String::new()) } } @@ -174,20 +182,21 @@ impl SimpleComponent for PreferencesPageModel { .transient_for_native(root) .launch(OpenDialogSettings::default()) .forward(&sender.input, |response| match response { - OpenDialogResponse::Accept(path) => PreferencesPageMsg::SetConfigPath(path), + OpenDialogResponse::Accept(path) => PreferencesPageMsg::SetConfigPath(Some(path)), OpenDialogResponse::Cancel => PreferencesPageMsg::Ignore, }); let flake_file_dialog = OpenDialog::builder() .transient_for_native(root) .launch(OpenDialogSettings::default()) .forward(&sender.input, |response| match response { - OpenDialogResponse::Accept(path) => PreferencesPageMsg::SetFlakePath(path), + OpenDialogResponse::Accept(path) => PreferencesPageMsg::SetFlakePath(Some(path)), OpenDialogResponse::Cancel => PreferencesPageMsg::Ignore, }); let model = PreferencesPageModel { hidden: true, - configpath: PathBuf::new(), + configpath: None, flake: None, + flakearg: None, open_dialog, flake_file_dialog, tracker: 0, @@ -201,41 +210,28 @@ impl SimpleComponent for PreferencesPageModel { fn update(&mut self, msg: Self::Input, sender: ComponentSender) { self.reset(); match msg { - PreferencesPageMsg::Show(path, flake) => { - self.configpath = path; - self.set_flake(flake); + PreferencesPageMsg::Show(config) => { + self.configpath = config.systemconfig.as_ref().map(|x| PathBuf::from(x)); + self.set_flake(config.flake.as_ref().map(|x| PathBuf::from(x))); + self.set_flakearg(config.flakearg.clone()); self.hidden = false; } PreferencesPageMsg::Open => self.open_dialog.emit(OpenDialogMsg::Open), PreferencesPageMsg::OpenFlake => self.flake_file_dialog.emit(OpenDialogMsg::Open), PreferencesPageMsg::SetConfigPath(path) => { self.configpath = path.clone(); - sender.output(AppMsg::UpdateSysconfig(path.to_string_lossy().to_string())); - } - PreferencesPageMsg::SetFlake(flake) => { - self.flake = flake; - sender.input(PreferencesPageMsg::ModifyFlake) + sender.output(AppMsg::UpdateSysconfig(path.map(|x| x.to_string_lossy().to_string()))); } PreferencesPageMsg::SetFlakePath(path) => { - self.flake = Some(( - path, - self.flake.as_ref().map(|x| x.1.clone()).unwrap_or_default(), - )); + self.flake = path; sender.input(PreferencesPageMsg::ModifyFlake) } PreferencesPageMsg::SetFlakeArg(arg) => { - self.flake = Some(( - self.flake.as_ref().map(|x| x.0.clone()).unwrap_or_default(), - arg, - )); + self.flakearg = arg; sender.input(PreferencesPageMsg::ModifyFlake) } PreferencesPageMsg::ModifyFlake => { - let out = self - .flake - .as_ref() - .map(|(path, arg)| format!("{}#{}", path.to_string_lossy(), arg)); - sender.output(AppMsg::UpdateFlake(out.map(|x| x.strip_suffix('#').unwrap_or(&x).to_string()))); + sender.output(AppMsg::UpdateFlake(self.flake.as_ref().map(|x| x.to_string_lossy().to_string()), self.flakearg.clone())); } _ => {} } diff --git a/src/ui/updatepage.rs b/src/ui/updatepage.rs index 0e31909..35f420f 100644 --- a/src/ui/updatepage.rs +++ b/src/ui/updatepage.rs @@ -27,6 +27,7 @@ pub struct UpdatePageModel { #[derive(Debug)] pub enum UpdatePageMsg { UpdateConfig(NscConfig), + UpdatePkgTypes(SystemPkgs, UserPkgs), Update(Vec, Vec), OpenRow(usize, InstallType), UpdateSystem, @@ -318,6 +319,11 @@ impl SimpleComponent for UpdatePageModel { self.config = config; self.updateworker.emit(UpdateAsyncHandlerMsg::UpdateConfig(self.config.clone())); } + UpdatePageMsg::UpdatePkgTypes(systype, usertype) => { + self.systype = systype.clone(); + self.usertype = usertype.clone(); + self.updateworker.emit(UpdateAsyncHandlerMsg::UpdatePkgTypes(self.systype.clone(), self.usertype.clone())); + } UpdatePageMsg::Update(updateuserlist, updatesystemlist) => { info!("UpdatePageMsg::Update"); debug!("UPDATEUSERLIST: {:?}", updateuserlist); diff --git a/src/ui/updateworker.rs b/src/ui/updateworker.rs index ac8fbb7..f90b347 100644 --- a/src/ui/updateworker.rs +++ b/src/ui/updateworker.rs @@ -15,7 +15,7 @@ use super::{ pub struct UpdateAsyncHandler { #[tracker::no_eq] process: Option>, - systemconfig: String, + systemconfig: Option, flakeargs: Option, syspkgs: SystemPkgs, userpkgs: UserPkgs, @@ -24,6 +24,7 @@ pub struct UpdateAsyncHandler { #[derive(Debug)] pub enum UpdateAsyncHandlerMsg { UpdateConfig(NscConfig), + UpdatePkgTypes(SystemPkgs, UserPkgs), UpdateChannels, UpdateChannelsAndSystem, @@ -53,7 +54,7 @@ impl Worker for UpdateAsyncHandler { fn init(params: Self::InitParams, _sender: relm4::ComponentSender) -> Self { Self { process: None, - systemconfig: String::default(), + systemconfig: None, flakeargs: None, syspkgs: params.syspkgs, userpkgs: params.userpkgs, @@ -65,14 +66,26 @@ impl Worker for UpdateAsyncHandler { match msg { UpdateAsyncHandlerMsg::UpdateConfig(config) => { self.systemconfig = config.systemconfig; - self.flakeargs = config.flake; + self.flakeargs = if let Some(flake) = config.flake { + if let Some(flakearg) = config.flakearg { + Some(format!("{}#{}", flake, flakearg)) + } else { + Some(flake) + } + } else { + None + } + } + UpdateAsyncHandlerMsg::UpdatePkgTypes(syspkgs, userpkgs) => { + self.syspkgs = syspkgs; + self.userpkgs = userpkgs; } UpdateAsyncHandlerMsg::UpdateChannels => { - let systenconfig = self.systemconfig.clone(); + let systemconfig = self.systemconfig.clone(); let flakeargs = self.flakeargs.clone(); let syspkgs = self.syspkgs.clone(); relm4::spawn(async move { - let result = runcmd(NscCmd::Channel, systenconfig, flakeargs, syspkgs).await; + let result = runcmd(NscCmd::Channel, systemconfig, flakeargs, syspkgs).await; match result { Ok(true) => { sender.output(UpdatePageMsg::DoneWorking); @@ -102,13 +115,14 @@ impl Worker for UpdateAsyncHandler { }); } UpdateAsyncHandlerMsg::RebuildSystem => { - let systenconfig = self.systemconfig.clone(); + let systemconfig = self.systemconfig.clone(); let flakeargs = self.flakeargs.clone(); let syspkgs = self.syspkgs.clone(); relm4::spawn(async move { let result = match syspkgs { - SystemPkgs::Legacy => runcmd(NscCmd::Rebuild, systenconfig, flakeargs, syspkgs).await, - SystemPkgs::Flake => runcmd(NscCmd::All, systenconfig, flakeargs, syspkgs).await, + SystemPkgs::Legacy => runcmd(NscCmd::Rebuild, systemconfig, flakeargs, syspkgs).await, + SystemPkgs::Flake => runcmd(NscCmd::All, systemconfig, flakeargs, syspkgs).await, + SystemPkgs::None => Ok(true), }; match result { Ok(true) => { @@ -174,7 +188,7 @@ impl Worker for UpdateAsyncHandler { async fn runcmd( cmd: NscCmd, - _systemconfig: String, + _systemconfig: Option, flakeargs: Option, syspkgs: SystemPkgs, ) -> Result> { @@ -245,6 +259,7 @@ async fn runcmd( .args(&rebuildargs) .stderr(Stdio::piped()) .spawn()?, + SystemPkgs::None => return Ok(true), }, }; @@ -286,7 +301,7 @@ async fn updateprofile() -> Result> { let mut cmd = tokio::process::Command::new("nix") .arg("profile") .arg("upgrade") - .arg("'.*'") + .arg(".*") // Allow updating potential unfree packages .arg("--impure") .stderr(Stdio::piped()) diff --git a/src/ui/welcome.rs b/src/ui/welcome.rs index ff4d619..19ec4e0 100644 --- a/src/ui/welcome.rs +++ b/src/ui/welcome.rs @@ -27,6 +27,7 @@ pub enum WelcomeMsg { Close, UpdateConfPath(PathBuf), UpdateFlakePath(PathBuf), + ClearConfPath, ClearFlakePath, OpenConf, OpenFlake, @@ -102,6 +103,14 @@ impl SimpleComponent for WelcomeModel { sender.input(WelcomeMsg::OpenConf); } }, + add_suffix = >k::Button { + set_halign: gtk::Align::Center, + set_valign: gtk::Align::Center, + set_icon_name: "user-trash-symbolic", + connect_clicked[sender] => move |_| { + sender.input(WelcomeMsg::ClearConfPath); + } + } }, }, gtk::ListBox { @@ -150,10 +159,8 @@ impl SimpleComponent for WelcomeModel { } }, }, - #[name(btn)] + #[name(continuebtn)] gtk::Button { - #[watch] - set_sensitive: model.confpath.is_some(), add_css_class: "pill", add_css_class: "suggested-action", set_label: "Continue", @@ -191,8 +198,6 @@ impl SimpleComponent for WelcomeModel { OpenDialogResponse::Cancel => WelcomeMsg::Ignore, }); - - let model = WelcomeModel { hidden: true, confpath: if Path::new("/etc/nixos/configuration.nix").exists() { Some(PathBuf::from("/etc/nixos/configuration.nix")) } else { None }, // parent_window.configpath.to_string(), @@ -205,7 +210,7 @@ impl SimpleComponent for WelcomeModel { let widgets = view_output!(); - widgets.btn.grab_focus(); + widgets.continuebtn.grab_focus(); ComponentParts { model, widgets } } @@ -217,14 +222,13 @@ impl SimpleComponent for WelcomeModel { self.hidden = false; } WelcomeMsg::Close => { - if let Some(confpath) = &self.confpath { - let config = NscConfig { - systemconfig: confpath.to_string_lossy().to_string(), - flake: self.flakepath.as_ref().map(|x| x.to_string_lossy().to_string()), - }; - sender.output(AppMsg::LoadConfig(config)); - self.hidden = true; - } + let config = NscConfig { + systemconfig: self.confpath.as_ref().map(|x| x.to_string_lossy().to_string()), + flake: self.flakepath.as_ref().map(|x| x.to_string_lossy().to_string()), + flakearg: None, + }; + sender.output(AppMsg::LoadConfig(config)); + self.hidden = true; } WelcomeMsg::UpdateConfPath(s) => { info!("Set configuration path to {}", s.to_string_lossy()); @@ -234,6 +238,9 @@ impl SimpleComponent for WelcomeModel { info!("Set flake path to {}", s.to_string_lossy()); self.set_flakepath(Some(s)); } + WelcomeMsg::ClearConfPath => { + self.set_confpath(None); + } WelcomeMsg::ClearFlakePath => { info!("Clear flake path"); self.set_flakepath(None); diff --git a/src/ui/window.rs b/src/ui/window.rs index 7f407e7..b50ea9d 100644 --- a/src/ui/window.rs +++ b/src/ui/window.rs @@ -31,6 +31,7 @@ enum MainPage { pub enum SystemPkgs { Legacy, Flake, + None, } #[derive(Debug, PartialEq, Clone)] @@ -87,8 +88,8 @@ pub struct AppModel { #[derive(Debug)] pub enum AppMsg { - UpdateSysconfig(String), - UpdateFlake(Option), + UpdateSysconfig(Option), + UpdateFlake(Option, Option), TryLoad, ReloadUpdate, LoadConfig(NscConfig), @@ -351,25 +352,31 @@ impl Component for AppModel { let (config, welcome) = if let Some(config) = getconfig() { debug!("Got config: {:?}", config); - if !Path::new(&config.systemconfig).exists() { - warn!("Invalid system config path: {}", config.systemconfig); - (config, true) - } else if let Some(flakepath) = &config.flake { + let mut out = false; + if let Some(configpath) = &config.systemconfig { + if !Path::new(configpath).exists() { + warn!("Invalid system config path: {}", configpath); + out = true + } + } + if let Some(flakepath) = &config.flake { if !Path::new(&flakepath).exists() { warn!("Invalid flake path: {}", flakepath); - (config, true) + out = true } else { - (config, false) + out = false } } else { - (config, false) + out = false } + (config, out) } else { // Show welcome page debug!("No config found"); (NscConfig { - systemconfig: String::from("/etc/nixos/configuration.nix"), + systemconfig: None, flake: None, + flakearg: None, }, true) }; @@ -392,22 +399,27 @@ impl Component for AppModel { UserPkgs::Env }; - let syspkgtype = match fs::read_to_string("/run/current-system/nixos-version") { - Ok(s) => { - if !Path::new("/nix/var/nix/profiles/per-user/root/channels/nixos").exists() || config.flake.is_some() { - SystemPkgs::Flake - } else if let Some(last) = s.split('.').last() { - if last.len() == 7 || last == "dirty" || last == "git" { + let syspkgtype = if config.systemconfig.is_none() { + SystemPkgs::None + } else { + match fs::read_to_string("/run/current-system/nixos-version") { + Ok(s) => { + if !Path::new("/nix/var/nix/profiles/per-user/root/channels/nixos").exists() || config.flake.is_some() { SystemPkgs::Flake + } else if let Some(last) = s.split('.').last() { + if last.len() == 7 || last == "dirty" || last == "git" { + SystemPkgs::Flake + } else { + SystemPkgs::Legacy + } } else { SystemPkgs::Legacy } - } else { - SystemPkgs::Legacy } + Err(_) => SystemPkgs::None, } - Err(_) => SystemPkgs::Legacy, }; + debug!("userpkgtype: {:?}", userpkgtype); debug!("syspkgtype: {:?}", syspkgtype); @@ -433,7 +445,7 @@ impl Component for AppModel { .launch(()) .forward(sender.input_sender(), identity); let installedpage = InstalledPageModel::builder() - .launch(userpkgtype.clone()) + .launch((syspkgtype.clone(), userpkgtype.clone())) .forward(sender.input_sender(), identity); let updatepage = UpdatePageModel::builder() .launch(UpdatePageInit { window: root.clone().upcast(), systype: syspkgtype.clone(), usertype: userpkgtype.clone(), config: config.clone() }) @@ -558,26 +570,55 @@ impl Component for AppModel { } AppMsg::UpdateSysconfig(systemconfig) => { self.config = NscConfig { - systemconfig, + systemconfig: systemconfig.clone(), flake: self.config.flake.clone(), + flakearg: self.config.flakearg.clone(), }; if editconfig(self.config.clone()).is_err() { warn!("Failed to update config"); } + + if systemconfig.is_some() { + if self.syspkgtype == SystemPkgs::None { + if self.config.flake.is_some() { + self.syspkgtype = SystemPkgs::Flake; + } else { + self.syspkgtype = SystemPkgs::Legacy; + } + } + } else { + self.syspkgtype = SystemPkgs::None; + } + self.pkgpage.emit(PkgMsg::UpdateConfig(self.config.clone())); self.updatepage.emit(UpdatePageMsg::UpdateConfig(self.config.clone())); - sender.input(AppMsg::UpdatePkgs(None)) + self.pkgpage.emit(PkgMsg::UpdatePkgTypes(self.syspkgtype.clone(), self.userpkgtype.clone())); + self.updatepage.emit(UpdatePageMsg::UpdatePkgTypes(self.syspkgtype.clone(), self.userpkgtype.clone())); + self.installedpage.emit(InstalledPageMsg::UpdatePkgTypes(self.syspkgtype.clone(), self.userpkgtype.clone())); + sender.input(AppMsg::UpdatePkgs(None)); + } - AppMsg::UpdateFlake(flake) => { + AppMsg::UpdateFlake(flake, flakearg) => { self.config = NscConfig { systemconfig: self.config.systemconfig.clone(), - flake, + flake: flake.clone(), + flakearg, }; if editconfig(self.config.clone()).is_err() { warn!("Failed to update config"); } + + if flake.is_some() { + self.syspkgtype = SystemPkgs::Flake; + } else { + self.syspkgtype = SystemPkgs::Legacy; + } + self.pkgpage.emit(PkgMsg::UpdateConfig(self.config.clone())); self.updatepage.emit(UpdatePageMsg::UpdateConfig(self.config.clone())); + self.pkgpage.emit(PkgMsg::UpdatePkgTypes(self.syspkgtype.clone(), self.userpkgtype.clone())); + self.updatepage.emit(UpdatePageMsg::UpdatePkgTypes(self.syspkgtype.clone(), self.userpkgtype.clone())); + self.installedpage.emit(InstalledPageMsg::UpdatePkgTypes(self.syspkgtype.clone(), self.userpkgtype.clone())); } AppMsg::Initialize(pkgs, recommendedapps, syspkgs, categoryrec, categoryall, profilepkgs) => { self.syspkgs = syspkgs; @@ -938,11 +979,15 @@ impl Component for AppModel { Ok(pcurrpkgs) } - let systempkgs = match getsystempkgs(&self.config.systemconfig) { - Ok(x) => x, - Err(_) => { - self.installedsystempkgs.clone() + let systempkgs = if let Some(sysconfig) = &self.config.systemconfig { + match getsystempkgs(sysconfig) { + Ok(x) => x, + Err(_) => { + self.installedsystempkgs.clone() + } } + } else { + HashSet::new() }; let userpkgs = match self.userpkgtype { @@ -1221,6 +1266,7 @@ impl Component for AppModel { }) } } + SystemPkgs::None => {} } self.updatepage.emit(UpdatePageMsg::Update(updateuseritems, updatesystemitems)); } @@ -1321,18 +1367,19 @@ impl Component for AppModel { let preferencespage = PreferencesPageModel::builder() .launch(self.mainwindow.clone().upcast()) .forward(sender.input_sender(), identity); - if let Some(flake) = &self.config.flake { - let flakeparts = flake.split('#').collect::>(); - if let Some(p) = flakeparts.first() { - let path = PathBuf::from(p); - let args = flakeparts.get(1).unwrap_or(&"").to_string(); - preferencespage.emit(PreferencesPageMsg::Show(PathBuf::from(&self.config.systemconfig), Some((path, args)))) - } else { - preferencespage.emit(PreferencesPageMsg::Show(PathBuf::from(&self.config.systemconfig), None)) - } - } else { - preferencespage.emit(PreferencesPageMsg::Show(PathBuf::from(&self.config.systemconfig), None)) - } + preferencespage.emit(PreferencesPageMsg::Show(self.config.clone())); + // if let Some(flake) = &self.config.flake { + // let flakeparts = flake.split('#').collect::>(); + // if let Some(p) = flakeparts.first() { + // let path = PathBuf::from(p); + // let args = flakeparts.get(1).unwrap_or(&"").to_string(); + // preferencespage.emit(PreferencesPageMsg::Show(PathBuf::from(&self.config.systemconfig), Some((path, args)))) + // } else { + // preferencespage.emit(PreferencesPageMsg::Show(PathBuf::from(&self.config.systemconfig), None)) + // } + // } else { + // preferencespage.emit(PreferencesPageMsg::Show(PathBuf::from(&self.config.systemconfig), None)) + // } } AppMsg::AddInstalledToWorkQueue(work) => { let p = match work.pkgtype { diff --git a/src/ui/windowloading.rs b/src/ui/windowloading.rs index 263558b..4f3466e 100644 --- a/src/ui/windowloading.rs +++ b/src/ui/windowloading.rs @@ -109,6 +109,9 @@ impl Worker for WindowAsyncHandler { } } } + SystemPkgs::None => { + HashMap::new() + } }; let profilepkgs = match userpkgs {