use crate::{
systemd::{
analyze::{self, Analyze},
errors::SystemdErrors,
},
widget::{close_window_shortcut, unit_file_panel::flatpak},
};
use std::cell::Ref;
use adw::prelude::AdwDialogExt;
use gettextrs::pgettext;
use gtk::{
gio::{self},
glib::{self, BoxedAnyObject, object::Cast},
pango::{AttrInt, AttrList, Weight},
prelude::*,
};
use tracing::{error, info};
const PAGE_BLAME: &str = "blame";
pub fn build_analyze_window() -> Result<adw::Window, SystemdErrors> {
let (analyse_box, store, total_time_label, stack) = build_analyze()?;
let header = adw::HeaderBar::builder()
.title_widget(&adw::WindowTitle::new(
&pgettext("analyse blame", "Analyse Blame"),
"",
))
.css_classes(["raised"])
.build();
let toolbar = adw::ToolbarView::builder().content(&analyse_box).build();
toolbar.add_top_bar(&header);
let window = adw::Window::builder()
.default_height(600)
.default_width(600)
.content(&toolbar)
.build();
close_window_shortcut(&window);
window.connect_show(move |window| {
fill_store(&store, &total_time_label, &stack, window);
});
Ok(window)
}
fn build_analyze() -> Result<(gtk::Box, gio::ListStore, gtk::Label, adw::ViewStack), SystemdErrors>
{
let unit_analyse_box = gtk::Box::builder()
.orientation(gtk::Orientation::Vertical)
.build();
let label_str = pgettext("analyse blame", "Total Time:");
let total_time_box = gtk::Box::builder()
.spacing(5)
.halign(gtk::Align::Center)
.build();
total_time_box.append(&{
let attribute_list = AttrList::new();
attribute_list.insert(AttrInt::new_weight(Weight::Medium));
gtk::Label::builder()
.label(label_str)
.attributes(&attribute_list)
.build()
});
unit_analyse_box.append(&total_time_box);
let attribute_list = AttrList::new();
attribute_list.insert(AttrInt::new_weight(Weight::Medium));
let total_time_label = gtk::Label::builder()
.label(pgettext("analyse blame", "seconds ..."))
.attributes(&attribute_list)
.selectable(true)
.focusable(false)
.build();
total_time_box.append(&total_time_label);
let (analyze_tree, store) = setup_systemd_analyze_tree()?;
let unit_analyse_scrolled_window = gtk::ScrolledWindow::builder()
.vexpand(true)
.focusable(true)
.child(&analyze_tree)
.build();
let stack = adw::ViewStack::new();
let spinner = adw::Spinner::new();
stack.add_named(&spinner, Some("spinner"));
stack.add_named(&unit_analyse_scrolled_window, Some(PAGE_BLAME));
unit_analyse_box.append(&stack);
Ok((unit_analyse_box, store, total_time_label, stack))
}
fn setup_systemd_analyze_tree() -> Result<(gtk::ColumnView, gio::ListStore), SystemdErrors> {
let store = gio::ListStore::new::<BoxedAnyObject>();
let single_selection = gtk::SingleSelection::new(Some(store.clone()));
let analyze_tree = gtk::ColumnView::builder()
.focusable(true)
.model(&single_selection)
.hexpand(true)
.build();
let col1factory = gtk::SignalListItemFactory::new();
let col2factory = gtk::SignalListItemFactory::new();
col1factory.connect_setup(move |_factory, item| {
let item = item.downcast_ref::<gtk::ListItem>().unwrap();
let row = gtk::Inscription::default();
item.set_child(Some(&row));
});
col1factory.connect_bind(move |_factory, item| {
let item = item.downcast_ref::<gtk::ListItem>().unwrap();
let child = item.child().and_downcast::<gtk::Inscription>().unwrap();
let entry = item.item().and_downcast::<BoxedAnyObject>().unwrap();
let r: Ref<Analyze> = entry.borrow();
child.set_text(Some(r.time.to_string().as_str()));
});
col2factory.connect_setup(move |_factory, item| {
let item = item.downcast_ref::<gtk::ListItem>().unwrap();
let row = gtk::Inscription::default();
item.set_child(Some(&row));
});
col2factory.connect_bind(move |_factory, item| {
let item = item.downcast_ref::<gtk::ListItem>().unwrap();
let child = item.child().and_downcast::<gtk::Inscription>().unwrap();
let entry = item.item().and_downcast::<BoxedAnyObject>().unwrap();
let r: Ref<Analyze> = entry.borrow();
child.set_text(Some(r.service.to_string().as_str()));
});
let col1_time = gtk::ColumnViewColumn::new(
Some(&pgettext("analyse blame", "Init time (ms)")),
Some(col1factory),
);
let col2_unit = gtk::ColumnViewColumn::new(
Some(&pgettext("analyse blame", "Running units")),
Some(col2factory),
);
col2_unit.set_expand(true);
analyze_tree.append_column(&col1_time);
analyze_tree.append_column(&col2_unit);
Ok((analyze_tree, store))
}
fn fill_store(
list_store: &gio::ListStore,
total_time_label: >k::Label,
stack: &adw::ViewStack,
window: &adw::Window,
) {
{
let list_store = list_store.clone();
let total_time_label = total_time_label.clone();
let stack = stack.clone();
let window = window.clone();
glib::spawn_future_local(async move {
let (sender, receiver) = tokio::sync::oneshot::channel();
systemd::runtime().spawn(async move {
let response = analyze::blame().await;
if let Err(e) = sender.send(response) {
error!("Channel closed unexpectedly: {e:?}");
}
});
let Ok(response) = receiver
.await
.inspect_err(|err| error!("Tokio channel dropped {err:?}"))
else {
stack.set_visible_child_name(PAGE_BLAME);
return;
};
list_store.remove_all();
let mut time_full = 0;
match response {
Ok(units) => {
for value in units {
time_full = value.time;
list_store.append(&BoxedAnyObject::new(value));
}
info!("Unit list refreshed! list size {}", list_store.n_items());
let time = (time_full as f32) / 1000f32;
let total_time_label_str =
crate::format2!(pgettext("analyse blame", "{} seconds"), time);
total_time_label.set_label(&total_time_label_str);
stack.set_visible_child_name(PAGE_BLAME);
}
Err(error) => {
match error {
SystemdErrors::CmdNoFreedesktopFlatpakPermission(_, _) => {
let dialog = flatpak::flatpak_permision_alert();
dialog.present(Some(&window));
dialog.connect_closed(move |_dialog| window.close());
display_error(error, &stack);
}
SystemdErrors::CmdNoFlatpakSpawn => {
display_error(error, &stack);
}
_ => stack.set_visible_child_name(PAGE_BLAME),
};
stack.set_visible_child_name(PAGE_BLAME);
}
}
});
}
}
fn display_error(error: SystemdErrors, stack: &adw::ViewStack) {
let tv = gtk::TextView::new();
let buf = tv.buffer();
let mut start_iter = buf.start_iter();
let gui_description = error.gui_description().unwrap_or_default();
buf.insert_markup(&mut start_iter, &gui_description);
const FLATPACK_PERMISSION: &str = "flatpak_permission";
stack.add_named(&tv, Some(FLATPACK_PERMISSION));
stack.set_visible_child_name(FLATPACK_PERMISSION)
}