Add nix profile support

Also broke a bunch of legacy/channel stuff I'll fix later
This commit is contained in:
Victor Fuentes 2022-08-31 23:56:44 -04:00
parent 175e7c23e9
commit eb3822c07a
No known key found for this signature in database
GPG Key ID: 0A88B68D6A9ACAE0
14 changed files with 1075 additions and 297 deletions

View File

@ -30,7 +30,6 @@ reqwest = { version = "0.11", features = ["blocking"] }
sha256 = "1.0"
image = "0.24"
spdx = "0.9"
# { git = "https://github.com/EmbarkStudios/spdx", version = "0.8" }
edit-distance = "2.1"
ijson = "0.1"
strum = "0.24"

View File

@ -51,11 +51,11 @@
},
"nixpkgs_2": {
"locked": {
"lastModified": 1661361016,
"narHash": "sha256-Bjf6ZDnDc6glTwIIItvwfcaeJ5zWFM6GYfPajSArdUY=",
"lastModified": 1661720780,
"narHash": "sha256-AJNGyaB2eKZAYaPNjBZOzap87yL+F9ZLaFzzMkvega0=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "b784c5ae63dd288375af1b4d37b8a27dd8061887",
"rev": "a63021a330d8d33d862a8e29924b42d73037dd37",
"type": "github"
},
"original": {

View File

@ -48,6 +48,7 @@
buildInputs = with pkgs; [
cargo
clippy
desktop-file-utils
rust-analyzer
rustc
rustfmt
@ -58,10 +59,13 @@
gtk4
gtksourceview5
libadwaita-git
meson
ninja
openssl
pandoc
pango
pkgconfig
polkit
wrapGAppsHook4
nixos-appstream-data
];

View File

@ -25,7 +25,16 @@ enum SubCommands {
rebuild: bool,
/// Run `nixos-rebuild` with the given arguments
arguments: Vec<String>,
}
},
Flake {
/// Whether to rebuild the system after updating flake
#[clap(short, long)]
rebuild: bool,
/// Path to the flake file
flakepath: String,
/// Run `nixos-rebuild` with the given arguments
arguments: Vec<String>,
},
}
fn main() {
@ -88,6 +97,24 @@ fn main() {
}
},
}
},
SubCommands::Flake { rebuild: dorebuild, flakepath, arguments } => {
match dorebuild {
true => match rebuild(arguments) {
Ok(_) => (),
Err(err) => {
eprintln!("{}", err);
std::process::exit(1);
}
},
false => match flake(&flakepath) {
Ok(_) => (),
Err(err) => {
eprintln!("{}", err);
std::process::exit(1);
}
},
}
}
}
}
@ -125,10 +152,28 @@ fn channel() -> Result<(), Box<dyn Error>> {
if x.success() {
Ok(())
} else {
eprintln!("nixos-rebuild failed with exit code {}", x.code().unwrap());
eprintln!("nix-channel failed with exit code {}", x.code().unwrap());
Err(Box::new(io::Error::new(
io::ErrorKind::Other,
"nix-channel failed",
)))
}
}
fn flake(path: &str) -> Result<(), Box<dyn Error>> {
let mut cmd = Command::new("nix")
.arg("flake")
.arg("upgrade")
.arg(path)
.spawn()?;
let x = cmd.wait()?;
if x.success() {
Ok(())
} else {
eprintln!("nix flake failed with exit code {}", x.code().unwrap());
Err(Box::new(io::Error::new(
io::ErrorKind::Other,
"nix flake failed",
)))
}
}

View File

@ -1,3 +1,5 @@
use std::{path::Path, fs};
pub mod ui;
pub mod parse;
pub mod config;

View File

@ -1,5 +1,6 @@
use ijson::IString;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::{
collections::HashMap,
env,
@ -21,13 +22,14 @@ struct NewPackage {
}
pub fn checkcache() -> Result<(), Box<dyn Error>> {
setuppkgscache()?;
setupupdatecache()?;
setupnewestver()?;
// setuplegacypkgscache().unwrap();
// setupupdatecache().unwrap();
// setupnewestver().unwrap();
setupflakepkgscache()?;
Ok(())
}
pub fn uptodate() -> Result<Option<(String, String)>, Box<dyn Error>> {
pub fn uptodatelegacy() -> Result<Option<(String, String)>, Box<dyn Error>> {
let cachedir = format!("{}/.cache/nix-software-center", env::var("HOME")?);
let oldversion = fs::read_to_string(format!("{}/sysver.txt", cachedir))?
.trim()
@ -39,11 +41,32 @@ pub fn uptodate() -> Result<Option<(String, String)>, Box<dyn Error>> {
println!("System is up to date");
Ok(None)
} else {
println!("OLD {:?} != NEW {:?}", oldversion, newversion);
Ok(Some((oldversion, newversion)))
}
}
pub fn uptodateflake() -> Result<Option<(String, String)>, Box<dyn Error>> {
let cachedir = format!("{}/.cache/nix-software-center", env::var("HOME")?);
let oldversion = fs::read_to_string(format!("{}/flakever.txt", cachedir))?
.trim()
.to_string();
let newversion = fs::read_to_string(format!("{}/newver.txt", cachedir))?
.trim()
.to_string();
if oldversion == newversion {
println!("System is up to date");
Ok(None)
} else {
println!("OLD {:?} != NEW {:?}", oldversion, newversion);
if let (Some(oldv), Some(newv)) = (oldversion.get(..8), newversion.get(..8)) {
println!("OLD {:?} != NEW {:?}", oldv, newv);
Ok(Some((oldv.to_string(), newv.to_string())))
} else {
Ok(Some((oldversion, newversion)))
}
}
}
pub fn channelver() -> Result<Option<(String, String)>, Box<dyn Error>> {
let cachedir = format!("{}/.cache/nix-software-center", env::var("HOME")?);
let oldversion = fs::read_to_string(format!("{}/chnver.txt", cachedir))?
@ -61,7 +84,24 @@ pub fn channelver() -> Result<Option<(String, String)>, Box<dyn Error>> {
}
}
fn setuppkgscache() -> Result<(), Box<dyn Error>> {
pub fn flakever() -> Result<Option<(String, String)>, Box<dyn Error>> {
let cachedir = format!("{}/.cache/nix-software-center", env::var("HOME")?);
let oldversion = fs::read_to_string(format!("{}/flakever.txt", cachedir))?
.trim()
.to_string();
let newversion = fs::read_to_string(format!("{}/newver.txt", cachedir))?
.trim()
.to_string();
if oldversion == newversion {
println!("Flake hashes match");
Ok(None)
} else {
println!("flakever {:?} != newver {:?}", oldversion, newversion);
Ok(Some((oldversion, newversion)))
}
}
fn setuplegacypkgscache() -> Result<(), Box<dyn Error>> {
let vout = Command::new("nix-instantiate")
.arg("-I")
.arg("nixpkgs=/nix/var/nix/profiles/per-user/root/channels/nixos")
@ -124,8 +164,187 @@ fn setuppkgscache() -> Result<(), Box<dyn Error>> {
Ok(())
}
fn setupflakepkgscache() -> Result<(), Box<dyn Error>> {
println!("SETUP FLAKES CACHE");
let cachedir = format!("{}/.cache/nix-software-center", env::var("HOME")?);
// First remove legacy files
if Path::new(format!("{}/chnver.txt", &cachedir).as_str()).exists() {
fs::remove_file(format!("{}/chnver.txt", &cachedir).as_str())?;
}
let vout = Command::new("nixos-version")
.arg("--json")
.output()?;
let versiondata: Value = serde_json::from_str(&String::from_utf8_lossy(&vout.stdout))?;
let rev = versiondata.get("nixpkgsRevision").unwrap().as_str().unwrap();
let dlver = versiondata.get("nixosVersion").unwrap().as_str().unwrap();
let mut relver = dlver.split('.').collect::<Vec<&str>>()[0..2].join(".");
println!("RELVER {}", relver);
if relver == "22.11" {
relver = "unstable".to_string();
}
fs::create_dir_all(&cachedir).expect("Failed to create cache directory");
let url = format!(
"https://channels.nixos.org/nixos-{}/packages.json.br",
relver
);
println!("VERSION {}", relver);
// let response = reqwest::blocking::get(url)?;
// if let Some(latest) = response.url().to_string().split('/').last() {
let cachedir = format!("{}/.cache/nix-software-center", env::var("HOME")?);
if !Path::new(&cachedir).exists() {
fs::create_dir_all(&cachedir).expect("Failed to create cache directory");
}
fn writesyspkgs(outfile: &str) -> Result<(), Box<dyn Error>> {
let output = Command::new("nix")
.arg("search")
.arg("--inputs-from")
.arg("/home/victor/nix")
.arg("nixpkgs")
.arg("--json")
.output()?;
let mut file = fs::File::create(outfile)?;
file.write_all(&output.stdout)?;
Ok(())
}
fn writeprofilepkgs(outfile: &str) -> Result<(), Box<dyn Error>> {
let output = Command::new("nix")
.arg("search")
.arg("nixpkgs")
.arg("--json")
.output()?;
let mut file = fs::File::create(outfile)?;
file.write_all(&output.stdout)?;
Ok(())
}
if !Path::new(&format!("{}/flakever.txt", &cachedir)).exists() {
let mut sysver = fs::File::create(format!("{}/flakever.txt", &cachedir))?;
sysver.write_all(rev.as_bytes())?;
writesyspkgs(&format!("{}/syspackages.json", &cachedir))?;
} else {
let oldver = fs::read_to_string(&Path::new(format!("{}/flakever.txt", &cachedir).as_str()))?;
let sysver = rev;
if oldver != sysver {
println!("OLD FLAKEVER: {}, != NEW: {}", oldver, sysver);
let mut sysver = fs::File::create(format!("{}/flakever.txt", &cachedir))?;
sysver.write_all(rev.as_bytes())?;
writesyspkgs(&format!("{}/syspackages.json", &cachedir))?;
}
}
if !Path::new(&format!("{}/syspackages.json", &cachedir)).exists() {
writesyspkgs(&format!("{}/syspackages.json", &cachedir))?;
}
// Check nix profile nixpkgs version
let client = reqwest::blocking::Client::builder()
.user_agent("request")
.build()?;
let response = client.get("https://api.github.com/repos/NixOS/nixpkgs/commits/nixpkgs-unstable").send()?;
if response.status().is_success() {
let profilerevjson = response.text()?;
let profilerevdata: Value = serde_json::from_str(&profilerevjson)?;
let profilerev = profilerevdata.get("sha").unwrap().as_str().unwrap();
println!("PROFILE REV {}", profilerev);
if !Path::new(&format!("{}/profilever.txt", &cachedir)).exists() {
let mut sysver = fs::File::create(format!("{}/profilever.txt", &cachedir))?;
sysver.write_all(profilerev.as_bytes())?;
writeprofilepkgs(&format!("{}/profilepackages.json", &cachedir))?;
} else {
let oldver = fs::read_to_string(&Path::new(format!("{}/profilever.txt", &cachedir).as_str()))?;
let sysver = profilerev;
if oldver != sysver {
println!("OLD PROFILEVER: {}, != NEW: {}", oldver, sysver);
let mut sysver = fs::File::create(format!("{}/profilever.txt", &cachedir))?;
sysver.write_all(profilerev.as_bytes())?;
writeprofilepkgs(&format!("{}/profilepackages.json", &cachedir))?;
} else {
println!("PROFILEVER UP TO DATE");
}
}
}
if !Path::new(&format!("{}/profilepackages.json", &cachedir)).exists() {
writeprofilepkgs(&format!("{}/profilepackages.json", &cachedir))?;
}
// Check newest nixpkgs version
let revurl = format!("https://channels.nixos.org/nixos-{}/git-revision", relver);
let response = reqwest::blocking::get(revurl)?;
let mut dl = false;
if response.status().is_success() {
let newrev = response.text()?;
println!("NEW REV: {}", newrev);
if Path::new(&format!("{}/newver.txt", &cachedir)).exists() {
let oldrev = fs::read_to_string(&format!("{}/newver.txt", &cachedir))?;
if oldrev != newrev {
dl = true;
}
} else {
dl = true;
}
let mut sysver = fs::File::create(format!("{}/newver.txt", &cachedir))?;
sysver.write_all(newrev.as_bytes())?;
// if !Path::new(&format!("{}/newver.txt", &cachedir)).exists() {
// }
}
// if Path::new(format!("{}/chnver.txt", &cachedir).as_str()).exists()
// && fs::read_to_string(&Path::new(format!("{}/chnver.txt", &cachedir).as_str()))? == dlver
// && Path::new(format!("{}/packages.json", &cachedir).as_str()).exists()
// {
// return Ok(());
// } else {
// let oldver = fs::read_to_string(&Path::new(format!("{}/chnver.txt", &cachedir).as_str()))?;
// let sysver = &dlver;
// // Change to debug msg
// println!("OLD: {}, != NEW: {}", oldver, sysver);
// }
// if Path::new(format!("{}/chnver.txt", &cachedir).as_str()).exists() {
// fs::remove_file(format!("{}/chnver.txt", &cachedir).as_str())?;
// }
// let mut sysver = fs::File::create(format!("{}/chnver.txt", &cachedir))?;
// sysver.write_all(dlver.as_bytes())?;
let outfile = format!("{}/packages.json", &cachedir);
if dl {
dlfile(&url, &outfile)?;
}
Ok(())
}
// nix-instantiate --eval -E '(builtins.getFlake "/home/victor/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 vout = Command::new("nix-instantiate")
.arg("-I")
.arg("nixpkgs=/nix/var/nix/profiles/per-user/root/channels/nixos")
.arg("<nixpkgs/lib>")
.arg("-A")
.arg("version")
.arg("--eval")
.arg("--json")
.output()?;
let dlver = String::from_utf8_lossy(&vout.stdout)
.to_string()
.replace('"', "");
// let dlver = fs::read_to_string("/run/current-system/nixos-version")?;
let mut relver = dlver.split('.').collect::<Vec<&str>>().join(".")[0..5].to_string();
@ -171,7 +390,7 @@ fn setupupdatecache() -> Result<(), Box<dyn Error>> {
dlfile(&url, &outfile)?;
let file = File::open(&outfile)?;
let reader = BufReader::new(file);
let pkgbase: NewPackageBase = simd_json::serde::from_reader(reader).unwrap();
let pkgbase: NewPackageBase = simd_json::serde::from_reader(reader)?;
let mut outbase = HashMap::new();
for (pkg, ver) in pkgbase.packages {
outbase.insert(pkg.clone(), ver.version.clone());

View File

@ -156,23 +156,29 @@ pub struct AppScreenshotImage {
pub url: String,
}
#[derive(Debug, Serialize, Deserialize)]
struct FlakeJson {
pname: IString,
version: IString,
}
pub async fn readpkgs() -> Result<HashMap<String, Package>, Box<dyn Error + Send + Sync>> {
let cachedir = format!("{}/.cache/nix-software-center/", env::var("HOME")?);
let cachefile = format!("{}/packages.json", cachedir);
let file = File::open(cachefile).unwrap();
let file = File::open(cachefile)?;
let reader = BufReader::new(file);
let pkgbase: PackageBase = simd_json::serde::from_reader(reader).unwrap();
let pkgbase: PackageBase = simd_json::serde::from_reader(reader)?;
let mut pkgs = pkgbase.packages;
println!("APPDATADIR {}", APPINFO);
let appdata = File::open(&format!("{}/xmls/nixos_x86_64_linux.yml.gz", APPINFO)).unwrap();
let appdata = File::open(&format!("{}/xmls/nixos_x86_64_linux.yml.gz", APPINFO))?;
let appreader = BufReader::new(appdata);
let mut d = GzDecoder::new(appreader);
let mut s = String::new();
d.read_to_string(&mut s).unwrap();
d.read_to_string(&mut s)?;
let mut files = s.split("\n---\n").collect::<Vec<_>>();
files.remove(0);
for f in files {
let appstream: AppData = serde_yaml::from_str(f).unwrap();
let appstream: AppData = serde_yaml::from_str(f)?;
if let Some(p) = pkgs.get_mut(&appstream.package.to_string()) {
p.appdata = Some(appstream);
}
@ -180,11 +186,32 @@ pub async fn readpkgs() -> Result<HashMap<String, Package>, Box<dyn Error + Sen
Ok(pkgs)
}
pub fn readsyspkgs() -> Result<HashMap<String, String>, Box<dyn Error + Send + Sync>> {
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);
let file = File::open(cachefile)?;
let reader = BufReader::new(file);
let newpkgs: HashMap<String, String> = simd_json::serde::from_reader(reader).unwrap();
let newpkgs: HashMap<String, String> = simd_json::serde::from_reader(reader)?;
Ok(newpkgs)
}
pub fn readflakesyspkgs() -> Result<HashMap<String, String>, Box<dyn Error + Send + Sync>> {
println!("READFLAKESYSPKGS");
let cachedir = format!("{}/.cache/nix-software-center/", env::var("HOME")?);
let cachefile = format!("{}/syspackages.json", cachedir);
let file = File::open(cachefile)?;
let reader = BufReader::new(file);
let newpkgs: HashMap<String, FlakeJson> = simd_json::serde::from_reader(reader)?;
let newpkgs = newpkgs.into_iter().filter_map(|(k, v)| if let Some(pkg) = k.strip_prefix("legacyPackages.x86_64-linux.") { Some((pkg.to_string(), v.version.to_string())) } else { None }).collect::<HashMap<_, _>>();
Ok(newpkgs)
}
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);
let file = File::open(cachefile)?;
let reader = BufReader::new(file);
let profilepkgs: HashMap<String, FlakeJson> = simd_json::serde::from_reader(reader)?;
let profilepkgs = profilepkgs.into_iter().filter_map(|(k, v)| if let Some(pkg) = k.strip_prefix("legacyPackages.x86_64-linux.") { Some((pkg.to_string(), v.version.to_string())) } else { None }).collect::<HashMap<_, _>>();
Ok(profilepkgs)
}

View File

@ -1,6 +1,7 @@
use crate::parse::config::NscConfig;
use super::pkgpage::{InstallType, PkgAction, PkgMsg, WorkPkg};
use super::window::{UserPkgs, SystemPkgs};
use relm4::*;
use std::error::Error;
use std::path::Path;
@ -17,6 +18,8 @@ pub struct InstallAsyncHandler {
systemconfig: String,
flakeargs: Option<String>,
pid: Option<u32>,
syspkgs: SystemPkgs,
userpkgs: UserPkgs,
}
#[derive(Debug)]
@ -27,18 +30,26 @@ pub enum InstallAsyncHandlerMsg {
SetPid(Option<u32>),
}
#[derive(Debug)]
pub struct InstallAsyncHandlerInit {
pub syspkgs: SystemPkgs,
pub userpkgs: UserPkgs,
}
impl Worker for InstallAsyncHandler {
type InitParams = ();
type InitParams = InstallAsyncHandlerInit;
type Input = InstallAsyncHandlerMsg;
type Output = PkgMsg;
fn init(_params: Self::InitParams, _sender: relm4::ComponentSender<Self>) -> Self {
fn init(params: Self::InitParams, _sender: relm4::ComponentSender<Self>) -> Self {
Self {
process: None,
work: None,
systemconfig: String::new(),
flakeargs: None,
pid: None,
syspkgs: params.syspkgs,
userpkgs: params.userpkgs,
tracker: 0,
}
}
@ -61,91 +72,188 @@ impl Worker for InstallAsyncHandler {
match work.action {
PkgAction::Install => {
println!("Installing user package: {}", work.pkg);
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 {
println!("CAUGHT LINE: {}", line);
}
match p.wait().await {
Ok(o) => {
if o.success() {
println!(
"Removed user package: {} success",
work.pkg
);
// println!("{}", String::from_utf8_lossy(&pstdout));
sender.output(PkgMsg::FinishedProcess(work))
} else {
println!(
"Removed user package: {} failed",
work.pkg
);
// println!("{}", String::from_utf8_lossy(&p.stderr));
sender.output(PkgMsg::FailedProcess(work));
println!("{:?}", self.userpkgs);
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 {
println!("CAUGHT LINE: {}", line);
}
}
Err(e) => {
println!("Error removing user package: {}", e);
sender.output(PkgMsg::FailedProcess(work));
}
match p.wait().await {
Ok(o) => {
if o.success() {
println!(
"Removed user package: {} success",
work.pkg
);
// println!("{}", String::from_utf8_lossy(&pstdout));
sender.output(PkgMsg::FinishedProcess(work))
} else {
println!(
"Removed user package: {} failed",
work.pkg
);
// println!("{}", String::from_utf8_lossy(&p.stderr));
sender.output(PkgMsg::FailedProcess(work));
}
}
Err(e) => {
println!("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("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 {
println!("CAUGHT LINE: {}", line);
}
match p.wait().await {
Ok(o) => {
if o.success() {
println!(
"Removed user package: {} success",
work.pkg
);
// println!("{}", String::from_utf8_lossy(&pstdout));
sender.output(PkgMsg::FinishedProcess(work))
} else {
println!(
"Removed user package: {} failed",
work.pkg
);
// println!("{}", String::from_utf8_lossy(&p.stderr));
sender.output(PkgMsg::FailedProcess(work));
}
}
Err(e) => {
println!("Error removing user package: {}", e);
sender.output(PkgMsg::FailedProcess(work));
}
}
}));
}
}
}
PkgAction::Remove => {
println!("Removing user package: {}", work.pkg);
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 {
println!("CAUGHT LINE: {}", line);
}
match p.wait().await {
Ok(o) => {
if o.success() {
println!(
"Removed user package: {} success",
work.pkg
);
// println!("{}", String::from_utf8_lossy(&pstdout));
sender.output(PkgMsg::FinishedProcess(work))
} else {
println!(
"Removed user package: {} failed",
work.pkg
);
// println!("{}", String::from_utf8_lossy(&p.stderr));
sender.output(PkgMsg::FailedProcess(work));
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 {
println!("CAUGHT LINE: {}", line);
}
}
Err(e) => {
println!("Error removing user package: {}", e);
sender.output(PkgMsg::FailedProcess(work));
}
match p.wait().await {
Ok(o) => {
if o.success() {
println!(
"Removed user package: {} success",
work.pkg
);
// println!("{}", String::from_utf8_lossy(&pstdout));
sender.output(PkgMsg::FinishedProcess(work))
} else {
println!(
"Removed user package: {} failed",
work.pkg
);
// println!("{}", String::from_utf8_lossy(&p.stderr));
sender.output(PkgMsg::FailedProcess(work));
}
}
Err(e) => {
println!("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 {
println!("CAUGHT LINE: {}", line);
}
match p.wait().await {
Ok(o) => {
if o.success() {
println!(
"Removed user package: {} success",
work.pkg
);
// println!("{}", String::from_utf8_lossy(&pstdout));
sender.output(PkgMsg::FinishedProcess(work))
} else {
println!(
"Removed user package: {} failed",
work.pkg
);
// println!("{}", String::from_utf8_lossy(&p.stderr));
sender.output(PkgMsg::FailedProcess(work));
}
}
Err(e) => {
println!("Error removing user package: {}", e);
sender.output(PkgMsg::FailedProcess(work));
}
}
}));
}
}
}
}
}
@ -276,8 +384,8 @@ async fn installsys(
let exe = match std::env::current_exe() {
Ok(mut e) => {
e.pop(); // root/bin
// e.pop(); // root/
// e.push("libexec"); // root/libexec
e.pop(); // root/
e.push("libexec"); // root/libexec
e.push("nsc-helper");
let x = e.to_string_lossy().to_string();
println!("CURRENT PATH {}", x);

View File

@ -9,6 +9,7 @@ use relm4::{factory::FactoryVecDeque, *};
use sha256::digest;
use std::collections::HashSet;
use std::convert::identity;
use std::io::Cursor;
use std::process::Command;
use std::{
env,
@ -27,6 +28,9 @@ use crate::parse::packages::StrOrVec;
use crate::ui::installworker::InstallAsyncHandlerMsg;
use super::installworker::InstallAsyncHandler;
use super::installworker::InstallAsyncHandlerInit;
use super::window::SystemPkgs;
use super::window::UserPkgs;
use super::{screenshotfactory::ScreenshotItem, window::AppMsg};
#[tracker::track]
@ -46,6 +50,9 @@ pub struct PkgModel {
maintainers: Vec<PkgMaintainer>,
launchable: Option<Launch>,
syspkgtype: SystemPkgs,
userpkgtype: UserPkgs,
#[tracker::no_eq]
screenshots: FactoryVecDeque<ScreenshotItem>,
#[tracker::no_eq]
@ -158,9 +165,15 @@ pub enum PkgAsyncMsg {
SetError(String, usize),
}
#[derive(Debug)]
pub struct PkgPageTypes {
pub syspkgs: SystemPkgs,
pub userpkgs: UserPkgs
}
#[relm4::component(pub)]
impl Component for PkgModel {
type Init = ();
type Init = PkgPageTypes;
type Input = PkgMsg;
type Output = AppMsg;
type Widgets = PkgWidgets;
@ -188,12 +201,26 @@ impl Component for PkgModel {
},
pack_end = &gtk::MenuButton {
#[watch]
set_label: match model.installtype {
InstallType::User => "User (nix-env)",
InstallType::System => "System (configuration.nix)",
set_label: match model.userpkgtype {
UserPkgs::Env => {
match model.installtype {
InstallType::User => "User (nix-env)",
InstallType::System => "System (configuration.nix)",
}
}
UserPkgs::Profile => {
match model.installtype {
InstallType::User => "User (nix profile)",
InstallType::System => "System (configuration.nix)",
}
}
},
#[wrap(Some)]
set_popover = &gtk::PopoverMenu::from_model(Some(&installtype)) {}
set_popover = &gtk::PopoverMenu::from_model(Some(&match model.userpkgtype {
UserPkgs::Env => installtype,
UserPkgs::Profile => installprofiletype,
})) {}
}
},
gtk::ScrolledWindow {
@ -899,6 +926,10 @@ impl Component for PkgModel {
"User (nix-env)" => NixEnvAction,
"System (configuration.nix)" => NixSystemAction,
},
installprofiletype: {
"User (nix-profile)" => NixProfileAction,
"System (configuration.nix)" => NixSystemAction,
},
runaction: {
"Run without installing" => LaunchAction,
"Open interactive shell" => TermShellAction,
@ -906,12 +937,12 @@ impl Component for PkgModel {
}
fn init(
(): Self::Init,
pkgtypes: Self::Init,
root: &Self::Root,
sender: ComponentSender<Self>,
) -> ComponentParts<Self> {
let installworker = InstallAsyncHandler::builder()
.detach_worker(())
.detach_worker(InstallAsyncHandlerInit { syspkgs: pkgtypes.syspkgs.clone(), userpkgs: pkgtypes.userpkgs.clone() })
.forward(sender.input_sender(), identity);
let config = getconfig();
installworker.emit(InstallAsyncHandlerMsg::SetConfig(config.clone()));
@ -936,6 +967,8 @@ impl Component for PkgModel {
// installinguserpkgs: HashSet::new(),
// installingsystempkgs: HashSet::new(),
// removinguserpkgs: HashSet::new(),
syspkgtype: pkgtypes.syspkgs,
userpkgtype: pkgtypes.userpkgs,
workqueue: HashSet::new(),
launchable: None,
tracker: 0,
@ -962,6 +995,15 @@ impl Component for PkgModel {
})
};
let nixprofile: RelmAction<NixProfileAction> = {
let sender = sender.clone();
RelmAction::new_stateless(move |_| {
println!("NIX PROFILE!");
sender.input(PkgMsg::SetInstallType(InstallType::User));
// sender.input(AppMsg::Increment);
})
};
let nixsystem: RelmAction<NixSystemAction> = {
let sender = sender.clone();
RelmAction::new_stateless(move |_| {
@ -972,6 +1014,7 @@ impl Component for PkgModel {
};
group.add_action(nixenv);
group.add_action(nixprofile);
group.add_action(nixsystem);
let actions = group.into_action_group();
@ -1124,6 +1167,7 @@ impl Component for PkgModel {
let sha = digest(&url);
let scrnpath = format!("{}/screenshots/{}", cachedir, sha);
let pkg = self.pkg.clone();
sender.command(move |out, shutdown| {
let url = url.clone();
let home = home.clone();
@ -1135,8 +1179,8 @@ impl Component for PkgModel {
if Path::new(&format!("{}.png", scrnpath)).exists() {
out.send(PkgAsyncMsg::LoadScreenshot(pkg, i, format!("{}.png", scrnpath)));
} else {
match reqwest::blocking::get(&url) {
Ok(mut response) => {
match reqwest::get(&url).await {
Ok(response) => {
if response.status().is_success() {
if !Path::new(&format!(
"{}/.cache/nix-software-center/screenshots",
@ -1156,43 +1200,49 @@ impl Component for PkgModel {
}
}
if let Ok(mut file) = File::create(&scrnpath) {
if response.copy_to(&mut file).is_ok() {
fn openimg(scrnpath: &str) -> Result<(), Box<dyn Error>> {
// let mut reader = Reader::new(Cursor::new(imgdata.buffer())).with_guessed_format().expect("Cursor io never fails");
let img = if let Ok(x) = image::load(BufReader::new(File::open(scrnpath)?), image::ImageFormat::Png) {
x
} else if let Ok(x) = image::load(BufReader::new(File::open(scrnpath)?), image::ImageFormat::Jpeg) {
x
} else if let Ok(x) = image::load(BufReader::new(File::open(scrnpath)?), image::ImageFormat::WebP) {
x
} else {
let imgdata = BufReader::new(File::open(scrnpath)?);
let format = image::guess_format(imgdata.buffer())?;
image::load(imgdata, format)?
};
let scaled = img.resize(640, 360, FilterType::Lanczos3);
let mut output = File::create(&format!("{}.png", scrnpath))?;
scaled.write_to(&mut output, ImageFormat::Png)?;
if let Err(e) = fs::remove_file(&scrnpath) {
eprintln!("{}", e);
}
Ok(())
}
match openimg(&scrnpath) {
Ok(_) => {
out.send(PkgAsyncMsg::LoadScreenshot(
pkg, i, format!("{}.png", scrnpath),
));
}
Err(_) => {
if let Ok(b) = response.bytes().await {
let mut content = Cursor::new(b);
if std::io::copy(&mut content, &mut file).is_ok() {
fn openimg(scrnpath: &str) -> Result<(), Box<dyn Error>> {
// let mut reader = Reader::new(Cursor::new(imgdata.buffer())).with_guessed_format().expect("Cursor io never fails");
let img = if let Ok(x) = image::load(BufReader::new(File::open(scrnpath)?), image::ImageFormat::Png) {
x
} else if let Ok(x) = image::load(BufReader::new(File::open(scrnpath)?), image::ImageFormat::Jpeg) {
x
} else if let Ok(x) = image::load(BufReader::new(File::open(scrnpath)?), image::ImageFormat::WebP) {
x
} else {
let imgdata = BufReader::new(File::open(scrnpath)?);
let format = image::guess_format(imgdata.buffer())?;
image::load(imgdata, format)?
};
let scaled = img.resize(640, 360, FilterType::Lanczos3);
let mut output = File::create(&format!("{}.png", scrnpath))?;
scaled.write_to(&mut output, ImageFormat::Png)?;
if let Err(e) = fs::remove_file(&scrnpath) {
eprintln!("{}", e);
}
out.send(PkgAsyncMsg::SetError(pkg, i));
Ok(())
}
match openimg(&scrnpath) {
Ok(_) => {
out.send(PkgAsyncMsg::LoadScreenshot(
pkg, i, format!("{}.png", scrnpath),
));
}
Err(_) => {
if let Err(e) = fs::remove_file(&scrnpath) {
eprintln!("{}", e);
}
out.send(PkgAsyncMsg::SetError(pkg, i));
}
}
}
}
} else {
out.send(PkgAsyncMsg::SetError(pkg, i));
eprintln!("Error: {}", response.status());
}
} else {
out.send(PkgAsyncMsg::SetError(pkg, i));
@ -1361,7 +1411,8 @@ impl Component for PkgModel {
self.installedsystempkgs.remove(&work.pkg);
// sender.output(AppMsg::RemoveSystemPkg(work.pkg));
}
}
};
sender.output(AppMsg::UpdateUpdatePkgs);
}
}
sender.output(AppMsg::UpdatePkgs(None));
@ -1465,6 +1516,7 @@ impl Component for PkgModel {
relm4::new_action_group!(ModeActionGroup, "mode");
relm4::new_stateless_action!(NixEnvAction, ModeActionGroup, "env");
relm4::new_stateless_action!(NixProfileAction, ModeActionGroup, "profile");
relm4::new_stateless_action!(NixSystemAction, ModeActionGroup, "system");
relm4::new_action_group!(RunActionGroup, "run");

View File

@ -5,12 +5,15 @@ use adw::prelude::*;
use relm4::*;
use relm4_components::open_dialog::*;
#[tracker::track]
#[derive(Debug)]
pub struct PreferencesPageModel {
hidden: bool,
configpath: PathBuf,
flake: Option<(PathBuf, String)>,
#[tracker::no_eq]
open_dialog: Controller<OpenDialog>,
#[tracker::no_eq]
flake_file_dialog: Controller<OpenDialog>,
}
@ -74,13 +77,13 @@ 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 {
// add_css_class: "flat",
// set_icon_name: "view-refresh-symbolic",
// connect_clicked[sender] => move |_| {
// sender.input(PreferencesPageMsg::SetConfigPath(PathBuf::from("/etc/nixos/configuration.nix")));
// }
// }
}
},
add = &adw::ActionRow {
@ -94,7 +97,10 @@ impl SimpleComponent for PreferencesPageModel {
sender.input(PreferencesPageMsg::SetFlake(None));
}
gtk::Inhibit(false)
}
} @switched,
#[track(model.changed(PreferencesPageModel::flake()))]
#[block_signal(switched)]
set_state: model.flake.is_some()
}
},
add = &adw::ActionRow {
@ -133,13 +139,13 @@ impl SimpleComponent for PreferencesPageModel {
sender.input(PreferencesPageMsg::OpenFlake);
}
},
gtk::Button {
add_css_class: "flat",
set_icon_name: "user-trash-symbolic",
connect_clicked[sender] => move |_| {
sender.input(PreferencesPageMsg::SetFlakePath(PathBuf::new()));
}
}
// gtk::Button {
// add_css_class: "flat",
// set_icon_name: "user-trash-symbolic",
// connect_clicked[sender] => move |_| {
// sender.input(PreferencesPageMsg::SetFlakePath(PathBuf::new()));
// }
// }
}
},
add = &adw::EntryRow {
@ -148,7 +154,10 @@ impl SimpleComponent for PreferencesPageModel {
set_title: "Flake arguments (--flake path/to/flake.nix#<THIS ENTRY>)",
connect_changed[sender] => move |x| {
sender.input(PreferencesPageMsg::SetFlakeArg(x.text().to_string()));
}
} @flakeentry,
#[track(model.changed(PreferencesPageModel::flake()))]
#[block_signal(flakeentry)]
set_text: &model.flake.as_ref().map(|(_, a)| a.to_string()).unwrap_or_default()
}
}
@ -181,6 +190,7 @@ impl SimpleComponent for PreferencesPageModel {
flake: None,
open_dialog,
flake_file_dialog,
tracker: 0,
};
let widgets = view_output!();
@ -189,10 +199,11 @@ 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.flake = flake;
self.set_flake(flake);
self.hidden = false;
println!("FLAKE {:?}", self.flake);
}

View File

@ -1,6 +1,6 @@
use crate::{parse::{cache::channelver, config::{getconfig, NscConfig}}, APPINFO};
use super::{pkgpage::InstallType, window::*, updatedialog::{UpdateDialogModel, UpdateDialogMsg}, updateworker::{UpdateAsyncHandler, UpdateAsyncHandlerMsg}};
use super::{pkgpage::InstallType, window::*, updatedialog::{UpdateDialogModel, UpdateDialogMsg}, updateworker::{UpdateAsyncHandler, UpdateAsyncHandlerMsg, UpdateAsyncHandlerInit}};
use adw::prelude::*;
use relm4::{factory::*, gtk::pango, *};
use std::{path::Path, convert::identity};
@ -18,6 +18,8 @@ pub struct UpdatePageModel {
#[tracker::no_eq]
updateworker: WorkerController<UpdateAsyncHandler>,
config: NscConfig,
systype: SystemPkgs,
usertype: UserPkgs,
updatetracker: u8,
}
@ -37,9 +39,15 @@ pub enum UpdatePageMsg {
FailedWorking,
}
pub struct UpdatePageInit {
pub window: gtk::Window,
pub systype: SystemPkgs,
pub usertype: UserPkgs,
}
#[relm4::component(pub)]
impl SimpleComponent for UpdatePageModel {
type InitParams = gtk::Window;
type InitParams = UpdatePageInit;
type Input = UpdatePageMsg;
type Output = AppMsg;
type Widgets = UpdatePageWidgets;
@ -178,7 +186,10 @@ impl SimpleComponent for UpdatePageModel {
gtk::Label {
set_halign: gtk::Align::Start,
add_css_class: "title-4",
set_label: "User (nix-env)",
set_label: match model.usertype {
UserPkgs::Env => "User (nix-env)",
UserPkgs::Profile => "User (nix profile)",
}
},
gtk::Button {
add_css_class: "suggested-action",
@ -263,15 +274,15 @@ impl SimpleComponent for UpdatePageModel {
}
fn init(
parent_window: Self::InitParams,
initparams: Self::InitParams,
root: &Self::Root,
sender: ComponentSender<Self>,
) -> ComponentParts<Self> {
let updatedialog = UpdateDialogModel::builder()
.launch(parent_window.upcast())
.launch(initparams.window.upcast())
.forward(sender.input_sender(), identity);
let updateworker = UpdateAsyncHandler::builder()
.detach_worker(())
.detach_worker(UpdateAsyncHandlerInit { syspkgs: initparams.systype.clone(), userpkgs: initparams.usertype.clone() })
.forward(sender.input_sender(), identity);
let config = getconfig();
@ -285,6 +296,8 @@ impl SimpleComponent for UpdatePageModel {
updatedialog,
updateworker,
config,
systype: initparams.systype,
usertype: initparams.usertype,
tracker: 0,
};
@ -304,6 +317,8 @@ impl SimpleComponent for UpdatePageModel {
self.updateworker.emit(UpdateAsyncHandlerMsg::UpdateConfig(self.config.clone()));
}
UpdatePageMsg::Update(updateuserlist, updatesystemlist) => {
println!("UPDATEUSERLIST: {:?}", updateuserlist);
println!("UPDATESYSTEMLIST: {:?}", updatesystemlist);
self.channelupdate = channelver().unwrap_or(None);
self.update_updatetracker(|_| ());
let mut updateuserlist_guard = self.updateuserlist.guard();

View File

@ -1,10 +1,13 @@
use std::{error::Error, path::Path, process::Stdio};
use relm4::*;
use std::{error::Error, path::Path, process::Stdio};
use tokio::io::AsyncBufReadExt;
use crate::parse::config::NscConfig;
use super::updatepage::UpdatePageMsg;
use super::{
updatepage::UpdatePageMsg,
window::{SystemPkgs, UserPkgs},
};
#[tracker::track]
#[derive(Debug)]
@ -13,6 +16,8 @@ pub struct UpdateAsyncHandler {
process: Option<JoinHandle<()>>,
systemconfig: String,
flakeargs: Option<String>,
syspkgs: SystemPkgs,
userpkgs: UserPkgs,
}
#[derive(Debug)]
@ -34,16 +39,23 @@ enum NscCmd {
All,
}
pub struct UpdateAsyncHandlerInit {
pub syspkgs: SystemPkgs,
pub userpkgs: UserPkgs,
}
impl Worker for UpdateAsyncHandler {
type InitParams = ();
type InitParams = UpdateAsyncHandlerInit;
type Input = UpdateAsyncHandlerMsg;
type Output = UpdatePageMsg;
fn init(_params: Self::InitParams, _sender: relm4::ComponentSender<Self>) -> Self {
fn init(params: Self::InitParams, _sender: relm4::ComponentSender<Self>) -> Self {
Self {
process: None,
systemconfig: String::default(),
flakeargs: None,
syspkgs: params.syspkgs,
userpkgs: params.userpkgs,
tracker: 0,
}
}
@ -57,9 +69,10 @@ impl Worker for UpdateAsyncHandler {
UpdateAsyncHandlerMsg::UpdateChannels => {
let systenconfig = self.systemconfig.clone();
let flakeargs = self.flakeargs.clone();
let syspkgs = self.syspkgs.clone();
relm4::spawn(async move {
println!("STARTED");
let result = runcmd(NscCmd::Channel, systenconfig, flakeargs).await;
let result = runcmd(NscCmd::Channel, systenconfig, flakeargs, syspkgs).await;
match result {
Ok(true) => {
println!("CHANNEL DONE");
@ -75,9 +88,10 @@ impl Worker for UpdateAsyncHandler {
UpdateAsyncHandlerMsg::UpdateChannelsAndSystem => {
let systenconfig = self.systemconfig.clone();
let flakeargs = self.flakeargs.clone();
let syspkgs = self.syspkgs.clone();
relm4::spawn(async move {
println!("STARTED");
let result = runcmd(NscCmd::All, systenconfig, flakeargs).await;
let result = runcmd(NscCmd::All, systenconfig, flakeargs, syspkgs).await;
match result {
Ok(true) => {
println!("ALL DONE");
@ -93,9 +107,10 @@ impl Worker for UpdateAsyncHandler {
UpdateAsyncHandlerMsg::RebuildSystem => {
let systenconfig = self.systemconfig.clone();
let flakeargs = self.flakeargs.clone();
let syspkgs = self.syspkgs.clone();
relm4::spawn(async move {
println!("STARTED");
let result = runcmd(NscCmd::Rebuild, systenconfig, flakeargs).await;
let result = runcmd(NscCmd::Rebuild, systenconfig, flakeargs, syspkgs).await;
match result {
Ok(true) => {
println!("REBUILD DONE");
@ -109,9 +124,13 @@ impl Worker for UpdateAsyncHandler {
});
}
UpdateAsyncHandlerMsg::UpdateUserPkgs => {
let userpkgs = self.userpkgs.clone();
relm4::spawn(async move {
println!("STARTED");
let result = updateenv().await;
let result = match userpkgs {
UserPkgs::Env => updateenv().await,
UserPkgs::Profile => updateprofile().await,
};
match result {
Ok(true) => {
println!("USER DONE");
@ -125,11 +144,26 @@ impl Worker for UpdateAsyncHandler {
});
}
UpdateAsyncHandlerMsg::UpdateAll => {
let systenconfig = self.systemconfig.clone();
let systemconfig = self.systemconfig.clone();
let flakeargs = self.flakeargs.clone();
let syspkgs = self.syspkgs.clone();
let userpkgs = self.userpkgs.clone();
relm4::spawn(async move {
println!("STARTED");
let result = runcmd(NscCmd::All, systenconfig, flakeargs).await;
// if syspkgs == SystemPkgs::Flake {
// if let Some(fa) = &flakeargs {
// match updateflake(fa.split('#').collect::<Vec<_>>().first().unwrap().to_string()).await {
// Ok(true) => {
// println!("FLAKE DONE");
// }
// _ => {
// println!("FLAKE FAILED");
// sender.output(UpdatePageMsg::FailedWorking);
// }
// }
// }
// }
let result = runcmd(NscCmd::All, systemconfig, flakeargs, syspkgs).await;
match result {
Ok(true) => {
println!("ALL pkexec DONE");
@ -159,12 +193,13 @@ async fn runcmd(
cmd: NscCmd,
_systemconfig: String,
flakeargs: Option<String>,
syspkgs: SystemPkgs,
) -> Result<bool, Box<dyn Error + Send + Sync>> {
let exe = match std::env::current_exe() {
Ok(mut e) => {
e.pop(); // root/bin
// e.pop(); // root/
// e.push("libexec"); // root/libexec
e.pop(); // root/
e.push("libexec"); // root/libexec
e.push("nsc-helper");
let x = e.to_string_lossy().to_string();
println!("CURRENT PATH {}", x);
@ -177,6 +212,9 @@ async fn runcmd(
Err(_) => String::from("nsc-helper"),
};
let flakepathsplit = flakeargs.clone().unwrap_or_default().to_string();
let flakepath = flakepathsplit.split('#').collect::<Vec<&str>>().first().cloned().unwrap_or_default();
let rebuildargs = if let Some(x) = flakeargs {
let mut v = vec![String::from("--flake")];
for arg in x.split(' ') {
@ -203,15 +241,28 @@ async fn runcmd(
.arg("channel")
.stderr(Stdio::piped())
.spawn()?,
NscCmd::All => tokio::process::Command::new("pkexec")
.arg(&exe)
.arg("channel")
.arg("--rebuild")
.arg("--")
.arg("switch")
.args(&rebuildargs)
.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")
.args(&rebuildargs)
.stderr(Stdio::piped())
.spawn()?,
},
};
println!("SENT INPUT");
@ -259,3 +310,33 @@ async fn updateenv() -> Result<bool, Box<dyn Error + Send + Sync>> {
Ok(false)
}
}
async fn updateprofile() -> Result<bool, Box<dyn Error + Send + Sync>> {
let mut cmd = tokio::process::Command::new("nix")
.arg("profile")
.arg("upgrade")
.arg("'.*'")
// Allow updating potential unfree packages
.arg("--impure")
.stderr(Stdio::piped())
.spawn()?;
println!("SENT INPUT");
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 {
println!("CAUGHT NIX PROFILE LINE: {}", line);
}
println!("READER DONE");
if cmd.wait().await?.success() {
println!("SUCCESS");
// sender.input(InstallAsyncHandlerMsg::SetPid(None));
Ok(true)
} else {
println!("FAILURE");
// sender.input(InstallAsyncHandlerMsg::SetPid(None));
Ok(false)
}
}

View File

@ -1,16 +1,17 @@
use std::{collections::{HashMap, HashSet}, convert::identity, error::Error, process::Command, fs, io, path::PathBuf};
use std::{collections::{HashMap, HashSet}, convert::identity, error::Error, process::Command, fs, io, path::{PathBuf, Path}, sync::Arc};
use ijson::IValue;
use relm4::{actions::*, factory::*, *};
use adw::prelude::*;
use edit_distance;
use serde_json::Value;
use spdx::Expression;
use crate::{parse::{packages::{Package, LicenseEnum, Platform}, cache::uptodate, config::{NscConfig, getconfig, editconfig}}, ui::installedpage::InstalledItem, APPINFO};
use crate::{parse::{packages::{Package, LicenseEnum, Platform}, cache::{uptodatelegacy, uptodateflake}, config::{NscConfig, getconfig, editconfig}}, ui::{installedpage::InstalledItem, pkgpage::PkgPageTypes}, APPINFO};
use super::{
categories::{PkgGroup, PkgCategory},
pkgtile::PkgTile,
pkgpage::{PkgModel, PkgMsg, PkgInitModel, self, InstallType, WorkPkg},
windowloading::{LoadErrorModel, LoadErrorMsg, WindowAsyncHandler, WindowAsyncHandlerMsg, CacheReturn}, searchpage::{SearchPageModel, SearchPageMsg, SearchItem}, installedpage::{InstalledPageModel, InstalledPageMsg}, updatepage::{UpdatePageModel, UpdatePageMsg, UpdateItem}, about::{AboutPageModel, AboutPageMsg}, preferencespage::{PreferencesPageModel, PreferencesPageMsg}, categorypage::{CategoryPageModel, CategoryPageMsg}, categorytile::CategoryTile,
windowloading::{LoadErrorModel, LoadErrorMsg, WindowAsyncHandler, WindowAsyncHandlerMsg, CacheReturn}, searchpage::{SearchPageModel, SearchPageMsg, SearchItem}, installedpage::{InstalledPageModel, InstalledPageMsg}, updatepage::{UpdatePageModel, UpdatePageMsg, UpdateItem, UpdatePageInit}, about::{AboutPageModel, AboutPageMsg}, preferencespage::{PreferencesPageModel, PreferencesPageMsg}, categorypage::{CategoryPageModel, CategoryPageMsg}, categorytile::CategoryTile,
};
#[derive(PartialEq)]
@ -25,6 +26,19 @@ enum MainPage {
CategoryPage,
}
#[derive(Debug, PartialEq, Clone)]
pub enum SystemPkgs {
Legacy,
Flake,
}
#[derive(Debug, PartialEq, Clone)]
pub enum UserPkgs {
Env,
Profile,
}
#[tracker::track]
pub struct AppModel {
application: adw::Application,
@ -40,10 +54,13 @@ pub struct AppModel {
#[tracker::no_eq]
pkgs: HashMap<String, Package>,
syspkgs: HashMap<String, String>,
profilepkgs: Option<HashMap<String, String>>,
// pkgset: HashSet<String>,
pkgitems: HashMap<String, PkgItem>,
installeduserpkgs: HashMap<String, String>,
installedsystempkgs: HashSet<String>,
syspkgtype: SystemPkgs,
userpkgtype: UserPkgs,
categoryrec: HashMap<PkgCategory, Vec<String>>,
categoryall: HashMap<PkgCategory, Vec<String>>,
#[tracker::no_eq]
@ -76,7 +93,7 @@ pub enum AppMsg {
ReloadUpdate,
Close,
LoadError(String, String),
Initialize(HashMap<String, Package>, Vec<String>, HashMap<String, String>, HashMap<PkgCategory, Vec<String>>, HashMap<PkgCategory, Vec<String>>),
Initialize(HashMap<String, Package>, Vec<String>, HashMap<String, String>, HashMap<PkgCategory, Vec<String>>, HashMap<PkgCategory, Vec<String>>, Option<HashMap<String, String>> /* profile pkgs */),
ReloadUpdateItems(HashMap<String, Package>, HashMap<String, String>),
OpenPkg(String),
FrontPage,
@ -336,6 +353,35 @@ impl Component for AppModel {
root: &Self::Root,
sender: ComponentSender<Self>,
) -> ComponentParts<Self> {
let userpkgtype = if let Ok(h) = std::env::var("HOME") {
if Path::new(&format!("{}/.nix-profile/manifest.json", h)).exists() {
UserPkgs::Profile
} else {
UserPkgs::Env
}
} else {
UserPkgs::Env
};
let syspkgtype = match fs::read_to_string("/run/current-system/nixos-version") {
Ok(s) => {
if let Some(last) = s.split('.').last() {
if last.len() == 7 {
SystemPkgs::Flake
} else {
SystemPkgs::Legacy
}
} else {
SystemPkgs::Legacy
}
}
Err(_) => SystemPkgs::Legacy,
};
println!("userpkgtype: {:?}", userpkgtype);
println!("syspkgtype: {:?}", syspkgtype);
let windowloading = WindowAsyncHandler::builder()
.detach_worker(())
.forward(sender.input_sender(), identity);
@ -343,7 +389,10 @@ impl Component for AppModel {
.launch(root.clone().upcast())
.forward(sender.input_sender(), identity);
let pkgpage = PkgModel::builder()
.launch(())
.launch(PkgPageTypes {
userpkgs: userpkgtype.clone(),
syspkgs: syspkgtype.clone(),
})
.forward(sender.input_sender(), identity);
let searchpage = SearchPageModel::builder()
.launch(())
@ -355,7 +404,8 @@ impl Component for AppModel {
.launch(())
.forward(sender.input_sender(), identity);
let updatepage = UpdatePageModel::builder()
.launch(root.clone().upcast())
// ADD FLAKE DETECTION
.launch(UpdatePageInit { window: root.clone().upcast(), systype: syspkgtype.clone(), usertype: userpkgtype.clone() })
.forward(sender.input_sender(), identity);
let viewstack = adw::ViewStack::new();
@ -375,6 +425,9 @@ impl Component for AppModel {
pkgitems: HashMap::new(),
installeduserpkgs: HashMap::new(),
installedsystempkgs: HashSet::new(),
profilepkgs: None,
syspkgtype,
userpkgtype,
categoryrec: HashMap::new(),
categoryall: HashMap::new(),
recommendedapps: FactoryVecDeque::new(gtk::FlowBox::new(), &sender.input),
@ -393,7 +446,7 @@ impl Component for AppModel {
tracker: 0,
};
model.windowloading.emit(WindowAsyncHandlerMsg::CheckCache(CacheReturn::Init));
model.windowloading.emit(WindowAsyncHandlerMsg::CheckCache(CacheReturn::Init, model.syspkgtype.clone(), model.userpkgtype.clone()));
let recbox = model.recommendedapps.widget();
let categorybox = model.categories.widget();
let viewstack = &model.viewstack;
@ -444,10 +497,10 @@ impl Component for AppModel {
match msg {
AppMsg::TryLoad => {
self.busy = true;
self.windowloading.emit(WindowAsyncHandlerMsg::CheckCache(CacheReturn::Init));
self.windowloading.emit(WindowAsyncHandlerMsg::CheckCache(CacheReturn::Init, self.syspkgtype.clone(), self.userpkgtype.clone()));
}
AppMsg::ReloadUpdate => {
self.windowloading.emit(WindowAsyncHandlerMsg::CheckCache(CacheReturn::Update));
self.windowloading.emit(WindowAsyncHandlerMsg::CheckCache(CacheReturn::Update, self.syspkgtype.clone(), self.userpkgtype.clone()));
}
AppMsg::Close => {
self.application.quit();
@ -479,8 +532,9 @@ impl Component for AppModel {
self.pkgpage.emit(PkgMsg::UpdateConfig(self.config.clone()));
self.updatepage.emit(UpdatePageMsg::UpdateConfig(self.config.clone()));
}
AppMsg::Initialize(pkgs, recommendedapps, syspkgs, categoryrec, categoryall) => {
AppMsg::Initialize(pkgs, recommendedapps, syspkgs, categoryrec, categoryall, profilepkgs) => {
self.syspkgs = syspkgs;
self.profilepkgs = profilepkgs;
self.categoryrec = categoryrec;
self.categoryall = categoryall;
let mut pkgitems = HashMap::new();
@ -791,7 +845,7 @@ impl Component for AppModel {
}
}
fn getuserpkgs() -> Result<HashMap<String, String>, Box<dyn Error>> {
fn getuserenvpkgs() -> Result<HashMap<String, String>, Box<dyn Error>> {
let out = Command::new("nix-env").arg("-q").arg("--json").output()?;
let data: IValue = serde_json::from_str(&String::from_utf8_lossy(&out.stdout))?;
let mut pcurrpkgs = HashMap::new();
@ -810,6 +864,45 @@ impl Component for AppModel {
Ok(pcurrpkgs)
}
fn getuserprofilepkgs() -> Result<HashMap<String, String>, Box<dyn Error>> {
let data: IValue = serde_json::from_str(&fs::read_to_string(Path::new(&format!("{}/.nix-profile/manifest.json", std::env::var("HOME")?)))?)?;
let mut pcurrpkgs = HashMap::new();
for pkg in data.as_object().unwrap()["elements"].as_array().unwrap().iter() {
if let Some(p) = pkg.get("attrPath") {
if let Some(pkgname) = p.as_string()
.unwrap()
// Change to current platform
.strip_prefix("legacyPackages.x86_64-linux.") {
if let Some(sp) = pkg.get("storePaths") {
if let Some(sp) = sp.as_array().unwrap().get(0) {
let storepath = sp.as_string().unwrap().to_string();
let output = Command::new("nix")
.arg("show-derivation")
.arg(&storepath)
.output()?;
let data: Value = serde_json::from_str(&String::from_utf8_lossy(&output.stdout))?;
if let Some(version) = data.as_object().unwrap().values().next().unwrap()["env"].get("version") {
let version = version.as_str().unwrap().to_string();
pcurrpkgs.insert(
pkgname.to_string(),
version,
);
} else {
pcurrpkgs.insert(
pkgname.to_string(),
String::default(),
);
}
}
}
}
}
}
println!("CURRENT PROFILE PKGS: {:?}", pcurrpkgs);
Ok(pcurrpkgs)
}
let systempkgs = match getsystempkgs(&self.config.systemconfig) {
Ok(x) => x,
Err(_) => {
@ -817,10 +910,22 @@ impl Component for AppModel {
}
};
let userpkgs = match getuserpkgs() {
Ok(out) => out,
Err(_) => {
self.installeduserpkgs.clone()
let userpkgs = match self.userpkgtype {
UserPkgs::Env => {
match getuserenvpkgs() {
Ok(out) => out,
Err(_) => {
self.installeduserpkgs.clone()
}
}
},
UserPkgs::Profile => {
match getuserprofilepkgs() {
Ok(out) => out,
Err(_) => {
self.installeduserpkgs.clone()
}
}
}
};
@ -880,34 +985,53 @@ impl Component for AppModel {
}
AppMsg::UpdateInstalledPkgs => {
let mut installeduseritems = vec![];
for installedpname in self.installeduserpkgs.keys() {
let possibleitems = self.pkgitems.iter().filter(|(_, x)| &x.pname == installedpname);
let count = possibleitems.clone().count();
match count {
1 => {
let (pkg, data) = possibleitems.collect::<Vec<_>>()[0];
installeduseritems.push(InstalledItem {
name: data.name.clone(),
pname: data.pname.clone(),
pkg: Some(pkg.clone()),
summary: data.summary.clone(),
icon: data.icon.clone(),
pkgtype: InstallType::User,
busy: self.installedpagebusy.contains(&(data.pname.clone(), InstallType::User)),
})
match self.userpkgtype {
UserPkgs::Env => {
for installedpname in self.installeduserpkgs.keys() {
let possibleitems = self.pkgitems.iter().filter(|(_, x)| &x.pname == installedpname);
let count = possibleitems.clone().count();
match count {
1 => {
let (pkg, data) = possibleitems.collect::<Vec<_>>()[0];
installeduseritems.push(InstalledItem {
name: data.name.clone(),
pname: data.pname.clone(),
pkg: Some(pkg.clone()),
summary: data.summary.clone(),
icon: data.icon.clone(),
pkgtype: InstallType::User,
busy: self.installedpagebusy.contains(&(data.pname.clone(), InstallType::User)),
})
}
2.. => {
installeduseritems.push(InstalledItem {
name: installedpname.clone(),
pname: installedpname.clone(),
pkg: None,
summary: None, //data.summary.clone(),
icon: None, //data.icon.clone(),
pkgtype: InstallType::User,
busy: self.installedpagebusy.contains(&(installedpname.clone(), InstallType::User)),
})
}
_ => {}
}
}
2.. => {
installeduseritems.push(InstalledItem {
name: installedpname.clone(),
pname: installedpname.clone(),
pkg: None,
summary: None, //data.summary.clone(),
icon: None, //data.icon.clone(),
pkgtype: InstallType::User,
busy: self.installedpagebusy.contains(&(installedpname.clone(), InstallType::User)),
})
}
UserPkgs::Profile => {
for installedpkg in self.installeduserpkgs.keys() {
if let Some(item) = self.pkgitems.get(installedpkg) {
installeduseritems.push(InstalledItem {
name: item.name.clone(),
pname: item.pname.clone(),
pkg: Some(item.pkg.clone()),
summary: item.summary.clone(),
icon: item.icon.clone(),
pkgtype: InstallType::User,
busy: self.installedpagebusy.contains(&(item.pkg.clone(), InstallType::User)),
})
}
}
_ => {}
}
}
@ -930,54 +1054,84 @@ impl Component for AppModel {
self.installedpage.emit(InstalledPageMsg::Update(installeduseritems, installedsystemitems));
}
AppMsg::UpdateUpdatePkgs => {
println!("InstalledUserPkgs: {:?}", self.installeduserpkgs);
println!("InstalledSystemPkgs: {:?}", self.installedsystempkgs);
let mut updateuseritems = vec![];
for (installedpname, version) in self.installeduserpkgs.iter() {
let possibleitems = self.pkgitems.iter().filter(|(_, x)| &x.pname == installedpname);
let count = possibleitems.clone().count();
match count {
1 => {
let (pkg, data) = possibleitems.collect::<Vec<_>>()[0];
if &data.version != version {
updateuseritems.push(UpdateItem {
name: data.name.clone(),
pname: data.pname.clone(),
pkg: Some(pkg.clone()),
summary: data.summary.clone(),
icon: data.icon.clone(),
pkgtype: InstallType::User,
verfrom: Some(version.clone()),
verto: Some(data.version.clone()),
})
} else {
println!("Pkg {} is up to date", pkg);
match self.userpkgtype {
UserPkgs::Env => {
for (installedpname, version) in self.installeduserpkgs.iter() {
let possibleitems = self.pkgitems.iter().filter(|(_, x)| &x.pname == installedpname);
let count = possibleitems.clone().count();
match count {
1 => {
let (pkg, data) = possibleitems.collect::<Vec<_>>()[0];
if &data.version != version {
updateuseritems.push(UpdateItem {
name: data.name.clone(),
pname: data.pname.clone(),
pkg: Some(pkg.clone()),
summary: data.summary.clone(),
icon: data.icon.clone(),
pkgtype: InstallType::User,
verfrom: Some(version.clone()),
verto: Some(data.version.clone()),
})
} else {
println!("Pkg {} is up to date", pkg);
}
}
2.. => {
let mut update = true;
for (pkg, _) in possibleitems {
if let Some(ver) = self.syspkgs.get(pkg) {
if version == ver {
update = false;
}
}
}
if update {
updateuseritems.push(UpdateItem {
name: installedpname.clone(),
pname: installedpname.clone(),
pkg: None,
summary: None, //data.summary.clone(),
icon: None, //data.icon.clone(),
pkgtype: InstallType::User,
verfrom: Some(version.clone()),
verto: None,
})
} else {
println!("Pkg {} is up to date", installedpname);
}
}
_ => {}
}
}
2.. => {
let mut update = true;
for (pkg, _) in possibleitems {
if let Some(ver) = self.syspkgs.get(pkg) {
if version == ver {
update = false;
}
UserPkgs::Profile => {
for (installedpkg, version) in &self.installeduserpkgs {
if let Some(item) = self.pkgitems.get(installedpkg) {
if let Some(profilepkgs) = &self.profilepkgs {
if let Some(newver) = profilepkgs.get(installedpkg) {
if version != newver {
updateuseritems.push(UpdateItem {
name: item.name.clone(),
pname: item.pname.clone(),
pkg: Some(item.pkg.clone()),
summary: item.summary.clone(),
icon: item.icon.clone(),
pkgtype: InstallType::User,
verfrom: Some(version.clone()),
verto: Some(newver.clone()),
})
} else {
println!("Pkg {} is up to date. Ver: {}", item.pname, version);
}
}
}
}
if update {
updateuseritems.push(UpdateItem {
name: installedpname.clone(),
pname: installedpname.clone(),
pkg: None,
summary: None, //data.summary.clone(),
icon: None, //data.icon.clone(),
pkgtype: InstallType::User,
verfrom: Some(version.clone()),
verto: None,
})
} else {
println!("Pkg {} is up to date", installedpname);
}
}
_ => {}
}
}
updateuseritems.sort_by(|a, b| a.name.to_lowercase().cmp(&b.name.to_lowercase()));
@ -1004,17 +1158,35 @@ impl Component for AppModel {
}
}
updatesystemitems.sort_by(|a, b| a.name.to_lowercase().cmp(&b.name.to_lowercase()));
if let Ok(Some((old, new))) = uptodate() {
updatesystemitems.insert(0, UpdateItem {
name: String::from("NixOS System"),
pname: String::new(),
pkg: None,
summary: Some(String::from("NixOS internal packages and modules")),
icon: None,
pkgtype: InstallType::System,
verfrom: Some(old),
verto: Some(new),
})
match self.syspkgtype {
SystemPkgs::Legacy => {
if let Ok(Some((old, new))) = uptodatelegacy() {
updatesystemitems.insert(0, UpdateItem {
name: String::from("NixOS System"),
pname: String::new(),
pkg: None,
summary: Some(String::from("NixOS internal packages and modules")),
icon: None,
pkgtype: InstallType::System,
verfrom: Some(old),
verto: Some(new),
})
}
}
SystemPkgs::Flake => {
if let Ok(Some((old, new))) = uptodateflake() {
updatesystemitems.insert(0, UpdateItem {
name: String::from("NixOS System"),
pname: String::new(),
pkg: None,
summary: Some(String::from("NixOS internal packages and modules")),
icon: None,
pkgtype: InstallType::System,
verfrom: Some(old),
verto: Some(new),
})
}
}
}
self.updatepage.emit(UpdatePageMsg::Update(updateuseritems, updatesystemitems));
}
@ -1119,7 +1291,18 @@ impl Component for AppModel {
let preferencespage = PreferencesPageModel::builder()
.launch(self.mainwindow.clone().upcast())
.forward(sender.input_sender(), identity);
preferencespage.emit(PreferencesPageMsg::Show(PathBuf::from(&self.config.systemconfig), None));
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) => {
println!("ADDING INSTALLED TO WORK QUEUE {:?}", work);

View File

@ -1,9 +1,13 @@
use super::window::AppMsg;
use super::window::SystemPkgs;
use crate::parse::cache::checkcache;
use crate::parse::packages::readflakesyspkgs;
use crate::parse::packages::readpkgs;
use crate::parse::packages::readsyspkgs;
use crate::parse::packages::readlegacysyspkgs;
use crate::parse::packages::Package;
use crate::parse::packages::readprofilepkgs;
use crate::ui::categories::PkgCategory;
use crate::ui::window::UserPkgs;
use rand::prelude::SliceRandom;
use rand::thread_rng;
use relm4::adw::prelude::*;
@ -15,7 +19,7 @@ pub struct WindowAsyncHandler;
#[derive(Debug)]
pub enum WindowAsyncHandlerMsg {
CheckCache(CacheReturn),
CheckCache(CacheReturn, SystemPkgs, UserPkgs),
}
#[derive(Debug, PartialEq)]
@ -35,7 +39,7 @@ impl Worker for WindowAsyncHandler {
fn update(&mut self, msg: Self::Input, sender: ComponentSender<Self>) {
match msg {
WindowAsyncHandlerMsg::CheckCache(cr) => {
WindowAsyncHandlerMsg::CheckCache(cr, syspkgs, userpkgs) => {
println!("CHECK CACHE");
relm4::spawn(async move {
match checkcache() {
@ -65,19 +69,47 @@ impl Worker for WindowAsyncHandler {
}
};
let newpkgs = match readsyspkgs() {
Ok(newpkgs) => newpkgs,
Err(_) => {
println!("FAILED TO LOAD NEW PKGS");
sender.output(AppMsg::LoadError(
String::from("Could not load new packages"),
String::from(
"Try connecting to the internet or launching the application again",
),
));
return;
println!("SYSTEM PKGS {:?}", syspkgs);
let newpkgs = match syspkgs {
SystemPkgs::Legacy => {
match readlegacysyspkgs() {
Ok(newpkgs) => newpkgs,
Err(_) => {
println!("FAILED TO LOAD NEW PKGS");
sender.output(AppMsg::LoadError(
String::from("Could not load new packages"),
String::from(
"Try connecting to the internet or launching the application again",
),
));
return;
}
}
}
SystemPkgs::Flake => {
match readflakesyspkgs() {
Ok(newpkgs) => newpkgs,
Err(_) => {
println!("FAILED TO LOAD NEW PKGS");
sender.output(AppMsg::LoadError(
String::from("Could not load new packages"),
String::from(
"Try connecting to the internet or launching the application again",
),
));
return;
}
}
}
};
// println!("SYSPKGS: {:#?}", syspkgs);
let profilepkgs = match userpkgs {
UserPkgs::Env => None,
UserPkgs::Profile => if let Ok(r) = readprofilepkgs() { Some(r) } else { None },
};
// println!("PROFILEPKGS: {:?}", profilepkgs);
println!("GOT PKGS");
@ -380,7 +412,7 @@ impl Worker for WindowAsyncHandler {
println!("SEND INIT");
match cr {
CacheReturn::Init => {
sender.output(AppMsg::Initialize(pkgs, recpicks, newpkgs, catpicks, catpkgs));
sender.output(AppMsg::Initialize(pkgs, recpicks, newpkgs, catpicks, catpkgs, profilepkgs));
}
CacheReturn::Update => {
sender.output(AppMsg::ReloadUpdateItems(pkgs, newpkgs));