diff --git a/Cargo.lock b/Cargo.lock index 32179ef..3a98614 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -198,9 +198,9 @@ checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" [[package]] name = "bytemuck" -version = "1.12.1" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f5715e491b5a1598fc2bef5a606847b5dc1d48ea625bd3c02c00de8285591da" +checksum = "5aec14f5d4e6e3f927cd0c81f72e5710d95ee9019fbeb4b3021193867491bfd8" [[package]] name = "byteorder" @@ -250,9 +250,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.73" +version = "1.0.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +checksum = "581f5dba903aac52ea3feb5ec4810848460ee833876f1f9b0fdeab1f19091574" [[package]] name = "cfg-expr" @@ -1230,9 +1230,9 @@ dependencies = [ [[package]] name = "hyper" -version = "0.14.20" +version = "0.14.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" +checksum = "abfba89e19b959ca163c7752ba59d737c1ceea53a5d31a149c805446fc958064" dependencies = [ "bytes", "futures-channel", @@ -1617,7 +1617,7 @@ checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" [[package]] name = "nix-data" version = "0.0.2" -source = "git+https://github.com/snowflakelinux/nix-data?rev=a5168c768e8cf8bd3e1afe1772f8e05e5b03ed95#a5168c768e8cf8bd3e1afe1772f8e05e5b03ed95" +source = "git+https://github.com/snowflakelinux/nix-data#33479e595f14142f15fb6ec29cc585352c2c7703" dependencies = [ "anyhow", "csv", @@ -1669,6 +1669,7 @@ dependencies = [ "serde_json", "serde_yaml", "sha256", + "sourceview5", "spdx", "sqlx", "tokio", @@ -1751,9 +1752,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" +checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" [[package]] name = "opaque-debug" @@ -1967,14 +1968,14 @@ checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" [[package]] name = "png" -version = "0.17.6" +version = "0.17.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f0e7f4c94ec26ff209cee506314212639d6c91b80afb82984819fafce9df01c" +checksum = "5d708eaf860a19b19ce538740d2b4bdeeb8337fa53f7738455e706623ad5c638" dependencies = [ "bitflags", "crc32fast", "flate2", - "miniz_oxide 0.5.4", + "miniz_oxide 0.6.2", ] [[package]] @@ -2500,6 +2501,42 @@ dependencies = [ "winapi", ] +[[package]] +name = "sourceview5" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "922cc28db6bec169868319262dd932f6403e5ce95dad0d2bb6fcc9ac03be7f10" +dependencies = [ + "bitflags", + "futures-channel", + "futures-core", + "gdk-pixbuf", + "gdk4", + "gio", + "glib", + "gtk4", + "libc", + "pango", + "sourceview5-sys", +] + +[[package]] +name = "sourceview5-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73277b2a53923aeecd212a89379dce7e6c687fe35fe6dd41d9b0d7b3d4c2eb0b" +dependencies = [ + "gdk-pixbuf-sys", + "gdk4-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "gtk4-sys", + "libc", + "pango-sys", + "system-deps", +] + [[package]] name = "spdx" version = "0.9.0" @@ -2777,9 +2814,9 @@ dependencies = [ [[package]] name = "tiff" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7259662e32d1e219321eb309d5f9d898b779769d81b76e762c07c8e5d38fcb65" +checksum = "9f71e422515e83e3ab8a03d4781d05ebf864fc61f4546e6ecffa58cbd34181a0" dependencies = [ "flate2", "jpeg-decoder", diff --git a/Cargo.toml b/Cargo.toml index 12b6149..e921a02 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ relm4 = { version = "0.5.0-beta.4", features = ["all"] } relm4-components = { package = "relm4-components", version = "0.5.0-beta.4"} adw = { package = "libadwaita", version = "0.2", features = ["v1_2", "gtk_v4_6"] } gtk = { package = "gtk4", version = "0.5", features = ["v4_6"] } +sourceview5 = { version = "0.5", features = ["v5_4"] } tokio = { version = "1.21", features = ["rt", "macros", "time", "rt-multi-thread", "sync", "process"] } tracker = "0.1" @@ -17,7 +18,7 @@ serde = { version = "1.0", features = ["derive"] } serde_yaml = "0.9" nix-editor = "0.3.0-beta.1" -nix-data = { git = "https://github.com/snowflakelinux/nix-data", rev = "a5168c768e8cf8bd3e1afe1772f8e05e5b03ed95" } +nix-data = { git = "https://github.com/snowflakelinux/nix-data" } sqlx = { version = "0.6", features = [ "runtime-tokio-native-tls" , "sqlite" ] } diff --git a/flake.nix b/flake.nix index 0fa6140..c5dfcdb 100644 --- a/flake.nix +++ b/flake.nix @@ -57,6 +57,7 @@ pango pkg-config polkit + sqlite wrapGAppsHook4 nixos-appstream-data ]; diff --git a/src/ui/installworker.rs b/src/ui/installworker.rs index d8a191f..17ba4a1 100644 --- a/src/ui/installworker.rs +++ b/src/ui/installworker.rs @@ -1,12 +1,13 @@ use super::pkgpage::{InstallType, PkgAction, PkgMsg, WorkPkg}; -use super::window::{SystemPkgs, UserPkgs}; +use super::rebuild::RebuildMsg; +use super::window::{SystemPkgs, UserPkgs, REBUILD_BROKER}; use log::*; use nix_data::config::configfile::NixDataConfig; use relm4::*; -use std::error::Error; +use anyhow::{Result, anyhow}; use std::path::Path; use std::process::Stdio; -use std::{fs, io}; +use std::fs; use tokio::io::{AsyncBufReadExt, AsyncWriteExt}; #[tracker::track] @@ -265,6 +266,7 @@ impl Worker for InstallAsyncHandler { } }, InstallType::System => { + REBUILD_BROKER.send(RebuildMsg::Show); if let Some(systemconfig) = systemconfig { match work.action { PkgAction::Install => { @@ -281,12 +283,15 @@ impl Worker for InstallAsyncHandler { { Ok(b) => { if b { + REBUILD_BROKER.send(RebuildMsg::FinishSuccess); sender.output(PkgMsg::FinishedProcess(work)) } else { + REBUILD_BROKER.send(RebuildMsg::FinishError(None)); sender.output(PkgMsg::FailedProcess(work)) } } Err(e) => { + REBUILD_BROKER.send(RebuildMsg::FinishError(None)); sender.output(PkgMsg::FailedProcess(work)); warn!("Error installing system package: {}", e); } @@ -307,12 +312,15 @@ impl Worker for InstallAsyncHandler { { Ok(b) => { if b { + REBUILD_BROKER.send(RebuildMsg::FinishSuccess); sender.output(PkgMsg::FinishedProcess(work)) } else { + REBUILD_BROKER.send(RebuildMsg::FinishError(None)); sender.output(PkgMsg::FailedProcess(work)) } } Err(e) => { + REBUILD_BROKER.send(RebuildMsg::FinishError(None)); sender.output(PkgMsg::FailedProcess(work)); warn!("Error removing system package: {}", e); } @@ -344,7 +352,7 @@ async fn installsys( systemconfig: String, flakeargs: Option, _sender: ComponentSender, -) -> Result> { +) -> Result { let mut p = pkg; let f = fs::read_to_string(&systemconfig)?; if let Ok(s) = nix_editor::read::getwithvalue(&f, "environment.systemPackages") { @@ -352,10 +360,7 @@ async fn installsys( p = format!("pkgs.{}", p); } } else { - return Err(Box::new(io::Error::new( - io::ErrorKind::InvalidData, - "Failed to write configuration.nix", - ))); + return Err(anyhow!("Failed to write configuration.nix")); } let out = match action { @@ -363,10 +368,7 @@ async fn installsys( match nix_editor::write::addtoarr(&f, "environment.systemPackages", vec![p]) { Ok(x) => x, Err(_) => { - return Err(Box::new(io::Error::new( - io::ErrorKind::InvalidData, - "Failed to write configuration.nix", - ))) + return Err(anyhow!("Failed to write configuration.nix")); } } } @@ -374,10 +376,7 @@ async fn installsys( match nix_editor::write::rmarr(&f, "environment.systemPackages", vec![p]) { Ok(x) => x, Err(_) => { - return Err(Box::new(io::Error::new( - io::ErrorKind::InvalidData, - "Failed to write configuration.nix", - ))) + return Err(anyhow!("Failed to write configuration.nix")); } } } @@ -419,13 +418,12 @@ async fn installsys( .arg(&systemconfig) .arg("--") .arg("switch") + .arg("--impure") .args(&rebuildargs) .stderr(Stdio::piped()) .stdin(Stdio::piped()) .spawn()?; - // sender.input(InstallAsyncHandlerMsg::SetPid(cmd.id())); - cmd.stdin.take().unwrap().write_all(out.as_bytes()).await?; let stderr = cmd.stderr.take().unwrap(); let reader = tokio::io::BufReader::new(stderr); @@ -433,6 +431,7 @@ async fn installsys( let mut lines = reader.lines(); while let Ok(Some(line)) = lines.next_line().await { trace!("CAUGHT LINE: {}", line); + REBUILD_BROKER.send(RebuildMsg::UpdateText(line)); } if cmd.wait().await?.success() { Ok(true) diff --git a/src/ui/mod.rs b/src/ui/mod.rs index d2b9873..5fe4d42 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,17 +1,17 @@ -pub mod window; -pub mod windowloading; -pub mod pkgtile; -pub mod categories; -pub mod pkgpage; -pub mod screenshotfactory; -pub mod installworker; -pub mod searchpage; -pub mod installedpage; -pub mod updatepage; -pub mod updatedialog; -pub mod updateworker; pub mod about; -pub mod preferencespage; +pub mod categories; pub mod categorypage; pub mod categorytile; +pub mod installedpage; +pub mod installworker; +pub mod pkgpage; +pub mod pkgtile; +pub mod preferencespage; +pub mod rebuild; +pub mod screenshotfactory; +pub mod searchpage; +pub mod updatepage; +pub mod updateworker; pub mod welcome; +pub mod window; +pub mod windowloading; diff --git a/src/ui/rebuild.rs b/src/ui/rebuild.rs new file mode 100644 index 0000000..e83be96 --- /dev/null +++ b/src/ui/rebuild.rs @@ -0,0 +1,244 @@ +use super::window::AppMsg; +use adw::prelude::*; +use log::{info, trace}; +use relm4::*; +use sourceview5::prelude::*; + +#[tracker::track] +pub struct RebuildModel { + hidden: bool, + text: String, + status: RebuildStatus, + config: String, + path: String, + flake: Option, + scheme: Option, +} + +#[derive(Debug)] +pub enum RebuildMsg { + Show, + FinishSuccess, + FinishError(Option), + UpdateText(String), + Close, + SetScheme(String), + Quit, +} + +#[derive(PartialEq)] +enum RebuildStatus { + Building, + Success, + Error, +} + +#[relm4::component(pub)] +impl SimpleComponent for RebuildModel { + type Init = gtk::Window; + type Input = RebuildMsg; + type Output = AppMsg; + type Widgets = RebuildWidgets; + + view! { + dialog = adw::Window { + set_transient_for: Some(&parent_window), + set_modal: true, + #[track(model.changed(RebuildModel::hidden()))] + set_default_width: 500, + #[track(model.changed(RebuildModel::hidden()))] + set_default_height: 200,//295), + set_resizable: true, + #[watch] + set_visible: !model.hidden, + add_css_class: "dialog", + add_css_class: "message", + gtk::Box { + set_orientation: gtk::Orientation::Vertical, + #[name(statusstack)] + gtk::Stack { + set_margin_top: 20, + set_transition_type: gtk::StackTransitionType::Crossfade, + set_vhomogeneous: false, + #[name(building)] + gtk::Box { + set_orientation: gtk::Orientation::Vertical, + set_spacing: 10, + gtk::Spinner { + #[watch] + set_spinning: true, + set_height_request: 60, + }, + gtk::Label { + set_label: "Building...", + add_css_class: "title-1", + }, + }, + #[name(success)] + gtk::Box { + set_orientation: gtk::Orientation::Vertical, + set_spacing: 10, + gtk::Image { + add_css_class: "success", + set_icon_name: Some("object-select-symbolic"), + set_pixel_size: 128, + }, + gtk::Label { + set_label: "Done!", + add_css_class: "title-1", + }, + gtk::Label { + set_label: "Rebuild successful!", + add_css_class: "dim-label", + } + }, + #[name(error)] + gtk::Box { + set_orientation: gtk::Orientation::Vertical, + set_spacing: 10, + gtk::Image { + add_css_class: "error", + set_icon_name: Some("dialog-error-symbolic"), + set_pixel_size: 128, + }, + gtk::Label { + set_label: "Error!", + add_css_class: "title-1", + }, + gtk::Label { + set_label: "Rebuild failed! See below for error message.", + add_css_class: "dim-label", + } + } + }, + gtk::Frame { + set_margin_all: 20, + #[name(scrollwindow)] + gtk::ScrolledWindow { + set_max_content_height: 500, + set_min_content_height: 100, + #[name(outview)] + sourceview5::View { + set_editable: false, + set_cursor_visible: false, + set_monospace: true, + set_top_margin: 5, + set_bottom_margin: 5, + set_left_margin: 5, + set_vexpand: true, + set_hexpand: true, + set_vscroll_policy: gtk::ScrollablePolicy::Minimum, + #[wrap(Some)] + set_buffer: outbuf = &sourceview5::Buffer { + #[track(model.changed(RebuildModel::scheme()))] + set_style_scheme: model.scheme.as_ref(), + #[track(model.changed(RebuildModel::text()))] + set_text: &model.text, + } + } + } + }, + gtk::Box { + add_css_class: "dialog-action-area", + set_orientation: gtk::Orientation::Horizontal, + set_homogeneous: true, + #[track(model.changed(RebuildModel::status()))] + set_visible: model.status != RebuildStatus::Building, + gtk::Button { + set_label: "Close", + #[track(model.changed(RebuildModel::status()))] + set_visible: model.status != RebuildStatus::Building, + connect_clicked[sender] => move |_| { + sender.input(RebuildMsg::Close) + } + } + } + } + } + } + + fn pre_view() { + match model.status { + RebuildStatus::Building => { + statusstack.set_visible_child(building); + } + RebuildStatus::Success => statusstack.set_visible_child(success), + RebuildStatus::Error => statusstack.set_visible_child(error), + } + } + + fn post_view() { + let adj = scrollwindow.vadjustment(); + if model.status == RebuildStatus::Building { + adj.set_upper(adj.upper() + 20.0); + } + adj.set_value(adj.upper()); + if model.status != RebuildStatus::Building { + outview.scroll_to_mark(&outview.buffer().get_insert(), 0.0, true, 0.0, 0.0); + scrollwindow.hadjustment().set_value(0.0); + } + } + + fn init( + parent_window: Self::Init, + root: &Self::Root, + sender: ComponentSender, + ) -> ComponentParts { + + let model = RebuildModel { + hidden: true, + text: String::new(), + status: RebuildStatus::Building, + config: String::new(), + path: String::new(), + flake: None, + scheme: None, + tracker: 0, + }; + + let widgets = view_output!(); + + ComponentParts { model, widgets } + } + + fn update(&mut self, msg: Self::Input, sender: ComponentSender) { + self.reset(); + match msg { + RebuildMsg::Show => { + self.update_hidden(|x| *x = false); + self.update_text(|x| x.clear()); + self.set_status(RebuildStatus::Building); + } + RebuildMsg::UpdateText(s) => { + info!("RebuildMsg::UpdateText({})", s); + let newtext = if self.text.is_empty() { + s + } else { + format!("{}\n{}", self.text, s) + }; + self.set_text(newtext); + trace!("NEWTEXT: {}", self.text); + } + RebuildMsg::FinishSuccess => { + self.set_status(RebuildStatus::Success); + } + RebuildMsg::FinishError(msg) => { + if let Some(s) = msg { + self.set_text(s) + } + self.update_hidden(|x| *x = false); + self.set_status(RebuildStatus::Error); + } + RebuildMsg::Close => { + self.update_hidden(|x| *x = true); + self.update_text(|x| x.clear()); + } + RebuildMsg::SetScheme(scheme) => { + self.set_scheme(sourceview5::StyleSchemeManager::default().scheme(&scheme)); + } + RebuildMsg::Quit => { + sender.output(AppMsg::Close); + } + } + } +} diff --git a/src/ui/updatedialog.rs b/src/ui/updatedialog.rs deleted file mode 100644 index 33639e8..0000000 --- a/src/ui/updatedialog.rs +++ /dev/null @@ -1,137 +0,0 @@ -use adw::prelude::*; -use relm4::*; - -use super::updatepage::UpdatePageMsg; - -#[derive(Debug)] -pub struct UpdateDialogModel { - hidden: bool, - message: String, - done: bool, - failed: bool, -} - -#[derive(Debug)] -pub enum UpdateDialogMsg { - Show(String), - Close, - Done, - Failed, -} - -#[relm4::component(pub)] -impl SimpleComponent for UpdateDialogModel { - type Init = gtk::Window; - type Input = UpdateDialogMsg; - type Output = UpdatePageMsg; - type Widgets = UpdateDialogWidgets; - - view! { - dialog = adw::Window { - #[watch] - set_visible: !model.hidden, - set_transient_for: Some(&parent_window), - set_modal: true, - set_resizable: false, - set_default_width: 500, - set_default_height: 200, - add_css_class: "dialog", - add_css_class: "message", - gtk::Box { - set_orientation: gtk::Orientation::Vertical, - set_halign: gtk::Align::Fill, - set_valign: gtk::Align::Fill, - set_hexpand: true, - set_vexpand: true, - gtk::Box { - set_orientation: gtk::Orientation::Vertical, - set_halign: gtk::Align::Center, - set_valign: gtk::Align::Center, - set_hexpand: true, - set_vexpand: true, - set_margin_all: 15, - set_spacing: 10, - gtk::Label { - #[watch] - set_visible: !model.message.is_empty(), - add_css_class: "title-1", - #[watch] - set_label: &model.message, - }, - if model.done { - gtk::Image { - add_css_class: "success", - set_icon_name: Some("emblem-ok-symbolic"), - set_pixel_size: 128, - } - } else if model.failed { - gtk::Image { - add_css_class: "error", - set_icon_name: Some("dialog-error-symbolic"), - set_pixel_size: 128, - } - } else { - gtk::Spinner { - #[watch] - set_visible: !model.done, - #[watch] - set_spinning: !model.done, - } - } - }, - gtk::Box { - #[watch] - set_visible: model.done || model.failed, - add_css_class: "dialog-action-area", - set_valign: gtk::Align::End, - set_vexpand: true, - set_orientation: gtk::Orientation::Horizontal, - set_homogeneous: true, - gtk::Button { - set_label: "Close", - connect_clicked[sender] => move |_| { - sender.input(UpdateDialogMsg::Close); - } - } - }, - } - } - } - - fn init( - parent_window: Self::Init, - root: &Self::Root, - sender: ComponentSender, - ) -> ComponentParts { - let model = UpdateDialogModel { - hidden: true, - done: false, - failed: false, - message: String::default(), - }; - - let widgets = view_output!(); - - ComponentParts { model, widgets } - } - - fn update(&mut self, msg: Self::Input, _sender: ComponentSender) { - match msg { - UpdateDialogMsg::Show(desc) => { - self.message = desc; - self.hidden = false; - self.done = false; - self.failed = false; - } - UpdateDialogMsg::Close => { - self.hidden = true; - } - UpdateDialogMsg::Done => { - self.done = true; - } - UpdateDialogMsg::Failed => { - self.failed = true; - } - } - } -} \ No newline at end of file diff --git a/src/ui/updatepage.rs b/src/ui/updatepage.rs index 34c7b71..9c15721 100644 --- a/src/ui/updatepage.rs +++ b/src/ui/updatepage.rs @@ -1,6 +1,6 @@ use crate::APPINFO; -use super::{pkgpage::InstallType, window::*, updatedialog::{UpdateDialogModel, UpdateDialogMsg}, updateworker::{UpdateAsyncHandler, UpdateAsyncHandlerMsg, UpdateAsyncHandlerInit}}; +use super::{pkgpage::InstallType, window::*, updateworker::{UpdateAsyncHandler, UpdateAsyncHandlerMsg, UpdateAsyncHandlerInit}, rebuild::RebuildMsg}; use adw::prelude::*; use nix_data::config::configfile::NixDataConfig; use relm4::{factory::*, gtk::pango, *}; @@ -16,8 +16,6 @@ pub struct UpdatePageModel { updatesystemlist: FactoryVecDeque, channelupdate: Option<(String, String)>, #[tracker::no_eq] - updatedialog: Controller, - #[tracker::no_eq] updateworker: WorkerController, config: NixDataConfig, systype: SystemPkgs, @@ -34,11 +32,10 @@ pub enum UpdatePageMsg { UpdateSystem, UpdateAllUser, UpdateUser(String), - UpdateChannels, - UpdateSystemAndChannels, + // UpdateChannels, + // UpdateSystemAndChannels, UpdateAll, DoneWorking, - DoneLoading, FailedWorking, } @@ -87,101 +84,101 @@ impl SimpleComponent for UpdatePageModel { } } }, - gtk::Box { - set_orientation: gtk::Orientation::Horizontal, - set_hexpand: true, - #[watch] - set_visible: model.channelupdate.is_some(), - gtk::Label { - set_halign: gtk::Align::Start, - add_css_class: "title-4", - set_label: "Channels", - }, - }, - gtk::ListBox { - set_valign: gtk::Align::Start, - add_css_class: "boxed-list", - set_selection_mode: gtk::SelectionMode::None, - #[watch] - set_visible: model.channelupdate.is_some(), - adw::PreferencesRow { - set_activatable: false, - set_can_focus: false, - #[wrap(Some)] - set_child = >k::Box { - set_orientation: gtk::Orientation::Horizontal, - set_hexpand: true, - set_spacing: 10, - set_margin_all: 10, - adw::Bin { - set_valign: gtk::Align::Center, - gtk::Image { - add_css_class: "icon-dropshadow", - set_halign: gtk::Align::Start, - set_icon_name: Some("application-x-addon"), - set_pixel_size: 64, - } - }, - gtk::Box { - set_orientation: gtk::Orientation::Vertical, - set_halign: gtk::Align::Fill, - set_valign: gtk::Align::Center, - set_hexpand: true, - set_spacing: 2, - gtk::Label { - set_halign: gtk::Align::Start, - set_label: "nixos", - set_ellipsize: pango::EllipsizeMode::End, - set_lines: 1, - set_wrap: true, - set_max_width_chars: 0, - }, - gtk::Label { - set_halign: gtk::Align::Start, - add_css_class: "dim-label", - add_css_class: "caption", - set_label: { - &(if let Some((old, new)) = &model.channelupdate { - format!("{} → {}", old, new) - } else { - String::default() - }) - }, - set_visible: model.channelupdate.is_some(), - set_ellipsize: pango::EllipsizeMode::End, - set_lines: 1, - set_wrap: true, - set_max_width_chars: 0, - }, - }, - gtk::Box { - set_orientation: gtk::Orientation::Vertical, - set_spacing: 5, - set_halign: gtk::Align::End, - set_valign: gtk::Align::Center, - gtk::Button { - add_css_class: "suggested-action", - set_valign: gtk::Align::Center, - set_halign: gtk::Align::End, - set_label: "Update channel and system", - set_can_focus: false, - connect_clicked[sender] => move |_| { - sender.input(UpdatePageMsg::UpdateSystemAndChannels); - } - }, - gtk::Button { - set_valign: gtk::Align::Center, - set_halign: gtk::Align::End, - set_label: "Update channel only", - set_can_focus: false, - connect_clicked[sender] => move |_| { - sender.input(UpdatePageMsg::UpdateChannels); - } - }, - } - } - } - }, + // gtk::Box { + // set_orientation: gtk::Orientation::Horizontal, + // set_hexpand: true, + // #[watch] + // set_visible: model.channelupdate.is_some(), + // gtk::Label { + // set_halign: gtk::Align::Start, + // add_css_class: "title-4", + // set_label: "Channels", + // }, + // }, + // gtk::ListBox { + // set_valign: gtk::Align::Start, + // add_css_class: "boxed-list", + // set_selection_mode: gtk::SelectionMode::None, + // #[watch] + // set_visible: model.channelupdate.is_some(), + // adw::PreferencesRow { + // set_activatable: false, + // set_can_focus: false, + // #[wrap(Some)] + // set_child = >k::Box { + // set_orientation: gtk::Orientation::Horizontal, + // set_hexpand: true, + // set_spacing: 10, + // set_margin_all: 10, + // adw::Bin { + // set_valign: gtk::Align::Center, + // gtk::Image { + // add_css_class: "icon-dropshadow", + // set_halign: gtk::Align::Start, + // set_icon_name: Some("application-x-addon"), + // set_pixel_size: 64, + // } + // }, + // gtk::Box { + // set_orientation: gtk::Orientation::Vertical, + // set_halign: gtk::Align::Fill, + // set_valign: gtk::Align::Center, + // set_hexpand: true, + // set_spacing: 2, + // gtk::Label { + // set_halign: gtk::Align::Start, + // set_label: "nixos", + // set_ellipsize: pango::EllipsizeMode::End, + // set_lines: 1, + // set_wrap: true, + // set_max_width_chars: 0, + // }, + // gtk::Label { + // set_halign: gtk::Align::Start, + // add_css_class: "dim-label", + // add_css_class: "caption", + // set_label: { + // &(if let Some((old, new)) = &model.channelupdate { + // format!("{} → {}", old, new) + // } else { + // String::default() + // }) + // }, + // set_visible: model.channelupdate.is_some(), + // set_ellipsize: pango::EllipsizeMode::End, + // set_lines: 1, + // set_wrap: true, + // set_max_width_chars: 0, + // }, + // }, + // gtk::Box { + // set_orientation: gtk::Orientation::Vertical, + // set_spacing: 5, + // set_halign: gtk::Align::End, + // set_valign: gtk::Align::Center, + // gtk::Button { + // add_css_class: "suggested-action", + // set_valign: gtk::Align::Center, + // set_halign: gtk::Align::End, + // set_label: "Update channel and system", + // set_can_focus: false, + // connect_clicked[sender] => move |_| { + // sender.input(UpdatePageMsg::UpdateSystemAndChannels); + // } + // }, + // gtk::Button { + // set_valign: gtk::Align::Center, + // set_halign: gtk::Align::End, + // set_label: "Update channel only", + // set_can_focus: false, + // connect_clicked[sender] => move |_| { + // sender.input(UpdatePageMsg::UpdateChannels); + // } + // }, + // } + // } + // } + // }, gtk::Box { set_orientation: gtk::Orientation::Horizontal, set_hexpand: true, @@ -234,7 +231,7 @@ impl SimpleComponent for UpdatePageModel { set_halign: gtk::Align::End, set_hexpand: true, set_valign: gtk::Align::Center, - set_label: "Update All", + set_label: "Update", connect_clicked[sender] => move |_|{ sender.input(UpdatePageMsg::UpdateSystem); }, @@ -282,9 +279,6 @@ impl SimpleComponent for UpdatePageModel { root: &Self::Root, sender: ComponentSender, ) -> ComponentParts { - let updatedialog = UpdateDialogModel::builder() - .launch(initparams.window.upcast()) - .forward(sender.input_sender(), identity); let updateworker = UpdateAsyncHandler::builder() .detach_worker(UpdateAsyncHandlerInit { syspkgs: initparams.systype.clone(), userpkgs: initparams.usertype.clone() }) .forward(sender.input_sender(), identity); @@ -297,7 +291,6 @@ impl SimpleComponent for UpdatePageModel { updatesystemlist: FactoryVecDeque::new(gtk::ListBox::new(), sender.input_sender()), channelupdate: None, updatetracker: 0, - updatedialog, updateworker, config, systype: initparams.systype, @@ -329,13 +322,13 @@ impl SimpleComponent for UpdatePageModel { info!("UpdatePageMsg::Update"); debug!("UPDATEUSERLIST: {:?}", updateuserlist); debug!("UPDATESYSTEMLIST: {:?}", updatesystemlist); - self.channelupdate = match nix_data::cache::channel::uptodate() { - Ok(x) => { - x - }, - Err(_) => None, - }; - debug!("CHANNELUPDATE: {:?}", self.channelupdate); + // self.channelupdate = match nix_data::cache::channel::uptodate() { + // Ok(x) => { + // x + // }, + // Err(_) => None, + // }; + // debug!("CHANNELUPDATE: {:?}", self.channelupdate); self.update_updatetracker(|_| ()); let mut updateuserlist_guard = self.updateuserlist.guard(); updateuserlist_guard.clear(); @@ -347,6 +340,16 @@ impl SimpleComponent for UpdatePageModel { for updatesystem in updatesystemlist { updatesystemlist_guard.push_back(updatesystem); } + updatesystemlist_guard.push_back(UpdateItem { + pkg: None, + name: "NixOS".to_string(), + pname: "nixos".to_string(), + summary: None, + icon: None, + pkgtype: InstallType::System, + verfrom: None, + verto: None, + }); } UpdatePageMsg::OpenRow(row, pkgtype) => match pkgtype { InstallType::User => { @@ -366,38 +369,36 @@ impl SimpleComponent for UpdatePageModel { } } }, - UpdatePageMsg::UpdateChannels => { - self.updatedialog.emit(UpdateDialogMsg::Show(String::from("Updating channels..."))); - self.updateworker.emit(UpdateAsyncHandlerMsg::UpdateChannels); - } - UpdatePageMsg::UpdateSystemAndChannels => { - self.updatedialog.emit(UpdateDialogMsg::Show(String::from("Updating system and channels..."))); - self.updateworker.emit(UpdateAsyncHandlerMsg::UpdateChannelsAndSystem); - } + // UpdatePageMsg::UpdateChannels => { + // self.updatedialog.emit(UpdateDialogMsg::Show(String::from("Updating channels..."))); + // self.updateworker.emit(UpdateAsyncHandlerMsg::UpdateChannels); + // } + // UpdatePageMsg::UpdateSystemAndChannels => { + // self.updatedialog.emit(UpdateDialogMsg::Show(String::from("Updating system and channels..."))); + // self.updateworker.emit(UpdateAsyncHandlerMsg::UpdateChannelsAndSystem); + // } UpdatePageMsg::UpdateSystem => { - self.updatedialog.emit(UpdateDialogMsg::Show(String::from("Updating system..."))); - self.updateworker.emit(UpdateAsyncHandlerMsg::RebuildSystem); + REBUILD_BROKER.send(RebuildMsg::Show); + self.updateworker.emit(UpdateAsyncHandlerMsg::UpdateSystem); } UpdatePageMsg::UpdateUser(pkg) => { info!("UPDATE USER PKG: {}", pkg); warn!("unimplemented"); } UpdatePageMsg::UpdateAllUser => { - self.updatedialog.emit(UpdateDialogMsg::Show(String::from("Updating all user packages..."))); + REBUILD_BROKER.send(RebuildMsg::Show); self.updateworker.emit(UpdateAsyncHandlerMsg::UpdateUserPkgs); } UpdatePageMsg::UpdateAll => { - self.updatedialog.emit(UpdateDialogMsg::Show(String::from("Updating everything..."))); + REBUILD_BROKER.send(RebuildMsg::Show); self.updateworker.emit(UpdateAsyncHandlerMsg::UpdateAll); } UpdatePageMsg::DoneWorking => { + REBUILD_BROKER.send(RebuildMsg::FinishSuccess); sender.output(AppMsg::UpdateInstalledPkgs); } - UpdatePageMsg::DoneLoading => { - self.updatedialog.emit(UpdateDialogMsg::Done); - } UpdatePageMsg::FailedWorking => { - self.updatedialog.emit(UpdateDialogMsg::Failed); + REBUILD_BROKER.send(RebuildMsg::FinishError(None)); } } } diff --git a/src/ui/updateworker.rs b/src/ui/updateworker.rs index 5dc7f1b..1bb509c 100644 --- a/src/ui/updateworker.rs +++ b/src/ui/updateworker.rs @@ -1,9 +1,12 @@ use nix_data::config::configfile::NixDataConfig; use relm4::*; -use std::{error::Error, path::Path, process::Stdio}; +use std::{path::Path, process::Stdio}; use tokio::io::AsyncBufReadExt; +use anyhow::Result; use log::*; +use crate::ui::{rebuild::RebuildMsg, window::REBUILD_BROKER}; + use super::{ updatepage::UpdatePageMsg, window::{SystemPkgs, UserPkgs}, @@ -25,8 +28,10 @@ pub enum UpdateAsyncHandlerMsg { UpdateConfig(NixDataConfig), UpdatePkgTypes(SystemPkgs, UserPkgs), - UpdateChannels, - UpdateChannelsAndSystem, + // UpdateChannels, + // UpdateChannelsAndSystem, + + UpdateSystem, RebuildSystem, UpdateUserPkgs, @@ -79,35 +84,52 @@ impl Worker for UpdateAsyncHandler { self.syspkgs = syspkgs; self.userpkgs = userpkgs; } - UpdateAsyncHandlerMsg::UpdateChannels => { + // UpdateAsyncHandlerMsg::UpdateChannels => { + // let systemconfig = self.systemconfig.clone(); + // let flakeargs = self.flakeargs.clone(); + // let syspkgs = self.syspkgs.clone(); + // relm4::spawn(async move { + // let result = runcmd(NscCmd::Channel, systemconfig, flakeargs, syspkgs).await; + // match result { + // Ok(true) => { + // sender.output(UpdatePageMsg::DoneWorking); + // } + // _ => { + // warn!("UPDATE CHANNEL FAILED"); + // sender.output(UpdatePageMsg::FailedWorking); + // } + // } + // }); + // } + // UpdateAsyncHandlerMsg::UpdateChannelsAndSystem => { + // let systenconfig = self.systemconfig.clone(); + // let flakeargs = self.flakeargs.clone(); + // let syspkgs = self.syspkgs.clone(); + // relm4::spawn(async move { + // let result = runcmd(NscCmd::All, systenconfig, flakeargs, syspkgs).await; + // match result { + // Ok(true) => { + // sender.output(UpdatePageMsg::DoneWorking); + // } + // _ => { + // warn!("UPDATE CHANNEL AND SYSTEM FAILED"); + // sender.output(UpdatePageMsg::FailedWorking); + // } + // } + // }); + // } + UpdateAsyncHandlerMsg::UpdateSystem => { let systemconfig = self.systemconfig.clone(); let flakeargs = self.flakeargs.clone(); let syspkgs = self.syspkgs.clone(); relm4::spawn(async move { - let result = runcmd(NscCmd::Channel, systemconfig, flakeargs, syspkgs).await; + let result = runcmd(NscCmd::All, systemconfig, flakeargs, syspkgs).await; match result { Ok(true) => { sender.output(UpdatePageMsg::DoneWorking); } _ => { - warn!("UPDATE CHANNEL FAILED"); - sender.output(UpdatePageMsg::FailedWorking); - } - } - }); - } - UpdateAsyncHandlerMsg::UpdateChannelsAndSystem => { - let systenconfig = self.systemconfig.clone(); - let flakeargs = self.flakeargs.clone(); - let syspkgs = self.syspkgs.clone(); - relm4::spawn(async move { - let result = runcmd(NscCmd::All, systenconfig, flakeargs, syspkgs).await; - match result { - Ok(true) => { - sender.output(UpdatePageMsg::DoneWorking); - } - _ => { - warn!("UPDATE CHANNEL AND SYSTEM FAILED"); + warn!("UPDATE SYSTEM FAILED"); sender.output(UpdatePageMsg::FailedWorking); } } @@ -190,7 +212,7 @@ async fn runcmd( _systemconfig: Option, flakeargs: Option, syspkgs: SystemPkgs, -) -> Result> { +) -> Result { let exe = match std::env::current_exe() { Ok(mut e) => { e.pop(); // root/bin @@ -255,6 +277,7 @@ async fn runcmd( .arg(flakepath) .arg("--") .arg("switch") + .arg("--impure") .args(&rebuildargs) .stderr(Stdio::piped()) .spawn()?, @@ -267,6 +290,7 @@ async fn runcmd( let mut lines = reader.lines(); while let Ok(Some(line)) = lines.next_line().await { + REBUILD_BROKER.send(RebuildMsg::UpdateText(line.to_string())); trace!("CAUGHT REBUILD LINE: {}", line); } if cmd.wait().await?.success() { @@ -276,7 +300,7 @@ async fn runcmd( } } -async fn updateenv() -> Result> { +async fn updateenv() -> Result { let mut cmd = tokio::process::Command::new("nix-env") .arg("-u") .stderr(Stdio::piped()) @@ -287,6 +311,7 @@ async fn updateenv() -> Result> { let mut lines = reader.lines(); while let Ok(Some(line)) = lines.next_line().await { + REBUILD_BROKER.send(RebuildMsg::UpdateText(line.to_string())); trace!("CAUGHT NIXENV LINE: {}", line); } if cmd.wait().await?.success() { @@ -296,7 +321,7 @@ async fn updateenv() -> Result> { } } -async fn updateprofile() -> Result> { +async fn updateprofile() -> Result { let mut cmd = tokio::process::Command::new("nix") .arg("profile") .arg("upgrade") @@ -311,6 +336,7 @@ async fn updateprofile() -> Result> { let mut lines = reader.lines(); while let Ok(Some(line)) = lines.next_line().await { + REBUILD_BROKER.send(RebuildMsg::UpdateText(line.to_string())); trace!("CAUGHT NIX PROFILE LINE: {}", line); } if cmd.wait().await?.success() { diff --git a/src/ui/window.rs b/src/ui/window.rs index 033fe5f..faa000f 100644 --- a/src/ui/window.rs +++ b/src/ui/window.rs @@ -4,7 +4,7 @@ use crate::{ config::{editconfig, getconfig}, packages::{AppData, LicenseEnum, PkgMaintainer, Platform}, }, - ui::{installedpage::InstalledItem, pkgpage::PkgPageInit, welcome::WelcomeMsg}, + ui::{installedpage::InstalledItem, pkgpage::PkgPageInit, welcome::WelcomeMsg, rebuild::RebuildMsg}, APPINFO, }; use adw::prelude::*; @@ -32,9 +32,11 @@ use super::{ searchpage::{SearchItem, SearchPageModel, SearchPageMsg}, updatepage::{UpdateItem, UpdatePageInit, UpdatePageModel, UpdatePageMsg}, welcome::WelcomeModel, - windowloading::{LoadErrorModel, LoadErrorMsg, WindowAsyncHandler, WindowAsyncHandlerMsg}, + windowloading::{LoadErrorModel, LoadErrorMsg, WindowAsyncHandler, WindowAsyncHandlerMsg}, rebuild::RebuildModel, }; +pub static REBUILD_BROKER: MessageBroker = MessageBroker::new(); + #[derive(PartialEq)] enum Page { FrontPage, @@ -109,6 +111,8 @@ pub struct AppModel { updatepage: Controller, viewstack: adw::ViewStack, installedpagebusy: Vec<(String, InstallType)>, + #[tracker::no_eq] + rebuild: Controller, } #[derive(Debug)] @@ -146,8 +150,8 @@ pub enum AppMsg { RemoveInstalledBusy(WorkPkg), OpenCategoryPage(PkgCategory), LoadCategory(PkgCategory), - UpdateRecPkgs(Vec), + SetDarkMode(bool), } #[derive(Debug, Clone, PartialEq, Eq)] @@ -490,6 +494,9 @@ impl Component for AppModel { config: config.clone(), }) .forward(sender.input_sender(), identity); + let rebuild = RebuildModel::builder() + .launch_with_broker(root.clone().upcast(), &REBUILD_BROKER) + .forward(sender.input_sender(), identity); let viewstack = adw::ViewStack::new(); let model = AppModel { @@ -523,9 +530,18 @@ impl Component for AppModel { updatepage, viewstack, installedpagebusy: vec![], + rebuild, tracker: 0, }; + { + let sender = sender.clone(); + adw::StyleManager::default() + .connect_dark_notify(move |x| sender.input(AppMsg::SetDarkMode(x.is_dark()))); + } + + sender.input(AppMsg::SetDarkMode(adw::StyleManager::default().is_dark())); + if welcome { let welcomepage = WelcomeModel::builder() .launch(root.clone().upcast()) @@ -582,6 +598,7 @@ impl Component for AppModel { frontvs.set_icon_name(Some("nsc-home-symbolic")); installedvs.set_icon_name(Some("nsc-installed-symbolic")); updatesvs.set_icon_name(Some("nsc-update-symbolic")); + ComponentParts { model, widgets } } @@ -1803,6 +1820,11 @@ FROM pkgs JOIN meta ON (pkgs.attribute = meta.attribute) WHERE pkgs.attribute = AppAsyncMsg::LoadCategory(category, catrec, catall) }); } + AppMsg::SetDarkMode(dark) => { + info!("AppMsg::SetDarkMode({})", dark); + let scheme = if dark { "Adwaita-dark" } else { "Adwaita" }; + self.rebuild.emit(RebuildMsg::SetScheme(scheme.to_string())); + } } }