sysd-manager 2.18.0

Application to empower user to manage their <b>systemd units</b> via Graphical User Interface. Not only are you able to make changes to the enablement and running status of each of the units, but you will also be able to view and modify their unit files and check the journal logs.
use std::rc::Rc;

use gettextrs::pgettext;
use gtk::{gio, glib};
use tracing::{debug, info, warn};

use crate::format2;
use crate::systemd::{self, data::UnitInfo, enums::UnitFileStatus, errors::SystemdErrors};

use super::UnitControlPanel;
use crate::gtk::prelude::*;
use crate::utils::palette::{blue, dark_blue};

pub(super) fn switch_ablement_state_set(
    control_panel: &UnitControlPanel,
    expected_new_status: UnitFileStatus,
    switch: &gtk::Switch,
    unit: &UnitInfo,
    call_back: Rc<Box<dyn Fn()>>,
) {
    info!(
        "switch_ablement_state_set Unit \"{}\" enablement \"{}\" sw_active {} sw_state {} expected_new_status {expected_new_status}",
        unit.primary(),
        unit.enable_status().as_str(),
        switch.is_active(),
        switch.state()
    );

    let current_enabled_status = unit.enable_status();

    if expected_new_status == current_enabled_status {
        set_switch_tooltip(current_enabled_status, switch, &unit.primary());
        return;
    }

    let switch = switch.clone();
    let control_panel = control_panel.clone();
    let unit = unit.clone();

    //let call_back: Box<dyn Fn()> = Box::new(call_back.clone());
    glib::spawn_future_local(async move {
        switch.set_sensitive(false);

        let primary_name = unit.primary();
        let level = unit.dbus_level();
        let enable_status = unit.enable_status();
        let enable_result = gio::spawn_blocking(move || {
            systemd::disenable_unit_file(&primary_name, level, enable_status, expected_new_status)
        })
        .await
        .expect("Task needs to finish successfully.");

        switch.set_sensitive(true);

        match enable_result {
            Ok(enablement_status_ret) => {
                let blue = dark_blue().get_color();

                let toast_info = format2!(
                    //toast message on success
                    pgettext(
                        "toast",
                        "Unit <span fgcolor='{0}' font_family='monospace' size='larger'>{}</span> has been successfully <span fgcolor='{0}'>{}</span>"
                    ),
                    blue,
                    unit.primary(),
                    expected_new_status,
                );

                debug!("{toast_info} {enablement_status_ret:?}");

                control_panel.add_toast_message(&toast_info, true);

                unit.set_enable_status(expected_new_status);

                switch.set_state(expected_new_status == UnitFileStatus::Enabled);
            }

            Err(error) => {
                let error_message = match error {
                    SystemdErrors::SystemCtlError(s) => s,
                    _ => format!("{error:?}"),
                };

                let (action_str, action_log) = match expected_new_status {
                    UnitFileStatus::Disabled => {
                        //toast message action on fail
                        (pgettext("toast", "Disabling"), "Disabling")
                    }
                    UnitFileStatus::Enabled => {
                        //toast message action on fail
                        (pgettext("toast", "Enabling"), "Enabling")
                    }
                    _ => ("???".to_owned(), "???"),
                };

                let blue = blue().get_color();

                let toast_info = format2!(
                    //toast message on fail, arg0 : Enabling/Disabling, arg1 : unit name
                    pgettext("toast", "{} unit {} has failed!"),
                    action_str,
                    format!(
                        "<span fgcolor='{}' font_family='monospace' size='larger'>{}</span> ",
                        blue,
                        unit.primary()
                    )
                );

                warn!(
                    "{action_log} unit {} has Failed! : {error_message}",
                    unit.primary()
                );

                control_panel.add_toast_message(&toast_info, true);
            }
        }

        handle_switch_sensivity(&switch, &unit, false);

        call_back()
    });
}

pub(super) fn reenable_unit(
    control_panel: &UnitControlPanel,
    switch: &gtk::Switch,
    unit: &UnitInfo,
    call_back: Rc<Box<dyn Fn()>>,
) {
    let expected_new_status = unit.enable_status(); //Expect new status
    info!(
        "Reenable unit Unit \"{}\" enablement \"{}\" sw_active {} sw_state {} expected_new_status {expected_new_status}",
        unit.primary(),
        unit.enable_status().as_str(),
        switch.is_active(),
        switch.state()
    );

    let switch = switch.clone();
    let control_panel = control_panel.clone();
    let unit = unit.clone();

    //let call_back: Box<dyn Fn()> = Box::new(call_back.clone());
    glib::spawn_future_local(async move {
        switch.set_sensitive(false);

        let primary_name = unit.primary();
        let level = unit.dbus_level();

        let enable_result = gio::spawn_blocking(move || {
            systemd::disenable_unit_file(
                &primary_name,
                level,
                UnitFileStatus::Enabled,
                UnitFileStatus::Disabled,
            )
            .map(|_ret| {
                systemd::disenable_unit_file(
                    &primary_name,
                    level,
                    UnitFileStatus::Disabled,
                    expected_new_status,
                )
            })
        })
        .await
        .expect("Task needs to finish successfully.");

        switch.set_sensitive(true);

        match enable_result {
            Ok(enablement_status_ret) => {
                let blue = dark_blue().get_color();

                //Toast message action on Reenable, in the rentance: ... has been successfully "Reenable"
                let action_str = pgettext("toast", "Reenable");

                let toast_info = format2!(
                    //toast message on success
                    pgettext(
                        "toast",
                        "Unit <span fgcolor='{0}' font_family='monospace' size='larger'>{}</span> has been successfully <span fgcolor='{0}'>{}</span>"
                    ),
                    blue,
                    unit.primary(),
                    action_str,
                );

                debug!("{toast_info} {enablement_status_ret:?}");

                control_panel.add_toast_message(&toast_info, true);

                unit.set_enable_status(expected_new_status);

                switch.set_state(expected_new_status == UnitFileStatus::Enabled);
            }

            Err(error) => {
                let error_message = match error {
                    SystemdErrors::SystemCtlError(s) => s,
                    _ => format!("{error:?}"),
                };

                //toast message action on fail
                let action_str = pgettext("toast", "Reenabling");

                let blue = blue().get_color();

                let toast_info = format2!(
                    //toast message on fail
                    pgettext(
                        "toast",
                        "{} unit <span fgcolor='{0}' font_family='monospace' size='larger'>{}</span> has failed!"
                    ),
                    blue,
                    action_str,
                    unit.primary()
                );

                warn!("{toast_info} : {error_message}");

                control_panel.add_toast_message(&toast_info, true);
            }
        }

        handle_switch_sensivity(&switch, &unit, false);

        call_back()
    });
}

//TODO do function to more constitency
fn set_switch_tooltip(enabled: UnitFileStatus, switch: &gtk::Switch, unit_name: &str) {
    let enabled = enabled == UnitFileStatus::Enabled;

    let action_text = if enabled {
        pgettext("controls", "Disable unit {}")
    } else {
        pgettext("controls", "Enable unit {}")
    };

    let blue = blue().get_color();
    let unit_str = format!(
        "<span fgcolor='{}' font_family='monospace' size='larger' weight='bold'>{}</span>",
        blue, unit_name
    );

    let tooltip = format2!(action_text, unit_str);

    switch.set_tooltip_markup(Some(&tooltip));
}

pub(super) fn handle_switch_sensivity(
    switch: &gtk::Switch,
    unit: &UnitInfo,
    check_current_state: bool,
) {
    let mut unit_file_state = unit.enable_status();

    if check_current_state {
        let switch = switch.clone();
        let unit = unit.clone();

        let primary_name = unit.primary();
        let level = unit.dbus_level();
        glib::spawn_future_local(async move {
            let current_state = gio::spawn_blocking(move || {
                systemd::get_unit_file_state(level, &primary_name)
                    .inspect_err(|err| {
                        info!(
                            "Get unit file state fail! For {:?} : {:?}",
                            primary_name, err
                        )
                    })
                    .unwrap_or(unit_file_state)
            })
            .await
            .expect("Task needs to finish successfully.");

            if current_state != unit_file_state {
                unit_file_state = current_state;
                unit.set_enable_status(unit_file_state);
            }

            handle_switch_sensivity_part2(&switch, &unit, unit_file_state);
        });
    } else {
        handle_switch_sensivity_part2(switch, unit, unit_file_state);
    }
}

fn handle_switch_sensivity_part2(
    switch: &gtk::Switch,
    unit: &UnitInfo,
    unit_file_state: UnitFileStatus,
) {
    if unit_file_state == UnitFileStatus::Enabled
        || unit_file_state == UnitFileStatus::EnabledRuntime
    {
        switch.set_state(true);
        switch.set_active(true);
    } else {
        switch.set_state(false);
        switch.set_active(false);
    }

    let sensitive = match unit_file_state {
        UnitFileStatus::Enabled | UnitFileStatus::EnabledRuntime | UnitFileStatus::Disabled => {
            set_switch_tooltip(unit_file_state, switch, &unit.primary());
            true
        }
        _ => {
            switch.set_tooltip_text(None);
            false
        }
    };

    switch.set_sensitive(sensitive);
}