Add systemless support

This commit is contained in:
Victor Fuentes 2022-09-25 02:33:12 -04:00
parent 8810c03fe8
commit 2a25c197f4
No known key found for this signature in database
GPG Key ID: 0A88B68D6A9ACAE0
12 changed files with 494 additions and 344 deletions

View File

@ -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<dyn Error>> {
// 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<dyn Error>> {
let dlver = fs::read_to_string("/run/current-system/nixos-version")?;
let output = Command::new("nix-instantiate")
.arg("--eval")
.arg("-E")
.arg("with import <nixpkgs> {}; pkgs.lib.version")
.output()?;
let dlver = String::from_utf8(output.stdout)?.replace("\"", "");
let mut relver = dlver.split('.').collect::<Vec<&str>>().join(".")[0..5].to_string();
@ -373,8 +385,12 @@ fn setupupdatecache() -> Result<(), Box<dyn Error>> {
}
fn setupnewestver() -> Result<(), Box<dyn Error>> {
let version = fs::read_to_string("/run/current-system/nixos-version")?;
let output = Command::new("nix-instantiate")
.arg("--eval")
.arg("-E")
.arg("with import <nixpkgs> {}; pkgs.lib.version")
.output()?;
let version = String::from_utf8(output.stdout)?.replace("\"", "");
let mut relver = version.split('.').collect::<Vec<&str>>().join(".")[0..5].to_string();
if version.len() >= 8 && &version[5..8] == "pre" {

View File

@ -10,8 +10,9 @@ use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
pub struct NscConfig {
pub systemconfig: String,
pub systemconfig: Option<String>,
pub flake: Option<String>,
pub flakearg: Option<String>,
}
pub fn getconfig() -> Option<NscConfig> {

View File

@ -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<HashMap<String, Package>, Box<dyn Error + Sen
let mut files = s.split("\n---\n").collect::<Vec<_>>();
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::<AppData>(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<HashMap<String, Package>, Box<dyn Error + Sen
pub fn readlegacysyspkgs() -> Result<HashMap<String, String>, Box<dyn Error + Send + Sync>> {
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<String, String> = simd_json::serde::from_reader(reader)?;
@ -199,6 +208,11 @@ pub fn readlegacysyspkgs() -> Result<HashMap<String, String>, Box<dyn Error + S
pub fn readflakesyspkgs() -> Result<HashMap<String, String>, Box<dyn Error + Send + Sync>> {
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<String, FlakeJson> = simd_json::serde::from_reader(reader)?;
@ -209,6 +223,11 @@ pub fn readflakesyspkgs() -> Result<HashMap<String, String>, Box<dyn Error + Se
pub fn readprofilepkgs() -> Result<HashMap<String, String>, Box<dyn Error + Send + Sync>> {
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<String, FlakeJson> = simd_json::serde::from_reader(reader)?;

View File

@ -13,12 +13,14 @@ pub struct InstalledPageModel {
#[tracker::no_eq]
installedsystemlist: FactoryVecDeque<InstalledItemModel>,
userpkgtype: UserPkgs,
systempkgtype: SystemPkgs,
updatetracker: u8,
}
#[derive(Debug)]
pub enum InstalledPageMsg {
Update(Vec<InstalledItem>, Vec<InstalledItem>),
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<Self>,
) -> ComponentParts<Self> {
@ -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 => {

View File

@ -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<JoinHandle<()>>,
work: Option<WorkPkg>,
systemconfig: String,
systemconfig: Option<String>,
flakeargs: Option<String>,
pid: Option<u32>,
syspkgs: SystemPkgs,
@ -26,6 +26,7 @@ pub struct InstallAsyncHandler {
#[derive(Debug)]
pub enum InstallAsyncHandlerMsg {
SetConfig(NscConfig),
SetPkgTypes(SystemPkgs, UserPkgs),
Process(WorkPkg),
CancelProcess,
SetPid(Option<u32>),
@ -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?;

View File

@ -136,6 +136,7 @@ pub struct PkgInitModel {
#[derive(Debug)]
pub enum PkgMsg {
UpdateConfig(NscConfig),
UpdatePkgTypes(SystemPkgs, UserPkgs),
Open(Box<PkgInitModel>),
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 = &gtk::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);

View File

@ -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<PathBuf>,
flake: Option<PathBuf>,
flakearg: Option<String>,
#[tracker::no_eq]
open_dialog: Controller<OpenDialog>,
#[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<PathBuf>),
SetFlakePath(Option<PathBuf>),
SetFlakeArg(Option<String>),
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#<THIS ENTRY>)",
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>) {
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()));
}
_ => {}
}

View File

@ -27,6 +27,7 @@ pub struct UpdatePageModel {
#[derive(Debug)]
pub enum UpdatePageMsg {
UpdateConfig(NscConfig),
UpdatePkgTypes(SystemPkgs, UserPkgs),
Update(Vec<UpdateItem>, Vec<UpdateItem>),
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);

View File

@ -15,7 +15,7 @@ use super::{
pub struct UpdateAsyncHandler {
#[tracker::no_eq]
process: Option<JoinHandle<()>>,
systemconfig: String,
systemconfig: Option<String>,
flakeargs: Option<String>,
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 {
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<String>,
flakeargs: Option<String>,
syspkgs: SystemPkgs,
) -> Result<bool, Box<dyn Error + Send + Sync>> {
@ -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<bool, Box<dyn Error + Send + Sync>> {
let mut cmd = tokio::process::Command::new("nix")
.arg("profile")
.arg("upgrade")
.arg("'.*'")
.arg(".*")
// Allow updating potential unfree packages
.arg("--impure")
.stderr(Stdio::piped())

View File

@ -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 = &gtk::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);

View File

@ -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<String>),
UpdateSysconfig(Option<String>),
UpdateFlake(Option<String>, Option<String>),
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::<Vec<&str>>();
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::<Vec<&str>>();
// 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 {

View File

@ -109,6 +109,9 @@ impl Worker for WindowAsyncHandler {
}
}
}
SystemPkgs::None => {
HashMap::new()
}
};
let profilepkgs = match userpkgs {