sysd-manager 2.19.4

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 glib::GString;
use systemd::enums::UnitType;
use tracing::error;

use crate::{
    consts::{
        AUTOMOUNT_IDLE_TIMEOUT_COL, AUTOMOUNT_IDLE_TIMEOUT_PROP, AUTOMOUNT_MOUNTED_COL,
        AUTOMOUNT_WHAT_COL, COL_ACTIVE, PATH_CONDITION_COL, PATH_PATH_COL, PATH_PATHS,
        SOCKET_LISTEN, SOCKET_LISTEN_COL, SOCKET_LISTEN_TYPE, TIME_LAST_TRIGGER_USEC,
        TIME_NEXT_ELAPSE_USEC_MONOTONIC, TIME_NEXT_ELAPSE_USEC_REALTIME, TIMER_TIME_LAST,
        TIMER_TIME_LEFT, TIMER_TIME_NEXT, TIMER_TIME_PASSED, WHERE_PROP,
    },
    widget::{
        unit_list::{COL_ID_UNIT, COL_ID_UNIT_FULL},
        unit_properties_selector::{
            data_browser::{INTERFACE_NAME, SPECIAL_INTERFACE_NAME},
            save::UnitColumn,
        },
    },
};

const COL_BUS: &str = "sysdm-bus";
const COL_PRESET: &str = "sysdm-preset";
const COL_STATE: &str = "sysdm-state";
const COL_TYPE: &str = "sysdm-type";
const COL_SUBSTATE: &str = "sysdm-sub";
const COL_LOAD: &str = "sysdm-load";
const COL_DESCRIPTION: &str = "sysdm-description";

#[derive(Debug, Eq, PartialEq, Hash, Clone)]
pub struct CustomProp {
    utype: UnitType,
    id: String,
    prop_idx: u8,
    signature: Option<String>,
}

impl CustomProp {
    fn property(&self) -> &str {
        &self.id[(self.prop_idx as usize)..]
    }
}

#[derive(Debug, Eq, Clone)]
pub enum SysdColumn {
    Name,
    FullName,
    Bus,
    Type,
    State,
    Preset,
    Load,
    Active,
    SubState,
    Description,
    TimerTimeNextElapseRT,
    TimerTimeLeftElapseMono,
    TimerTimePassed,
    TimerTimeLast,
    SocketListen,
    SocketListenType,
    PathCondition,
    Path,
    AutomountWhat,
    AutomountMounted,
    AutomountIdleTimeOut,
    Custom(CustomProp),
}

impl SysdColumn {
    pub(crate) fn new_from_props(
        property_name: &str,
        interface: &str,
        signature: Option<String>,
    ) -> SysdColumn {
        // let signature = signature.map(|s| s.to_owned());

        let r = match interface {
            "" | SPECIAL_INTERFACE_NAME | INTERFACE_NAME => {
                Self::new(property_name.to_owned(), signature)
            }
            _ => {
                let ut = UnitType::from_intreface(interface);
                let new_id = format!("{}@{property_name}", ut.as_str());
                Self::new(new_id, signature)
            }
        };

        match r {
            Ok(a) => a,
            Err(e) => e.1,
        }
    }

    pub fn new(
        id: String,
        prop_type: Option<String>,
    ) -> Result<SysdColumn, (SysdColumnNonConformity, SysdColumn)> {
        let col = match id.as_str() {
            COL_ID_UNIT => SysdColumn::Name,
            COL_ID_UNIT_FULL => SysdColumn::FullName,
            COL_BUS => SysdColumn::Bus,
            COL_TYPE => SysdColumn::Type,
            COL_STATE => SysdColumn::State,
            COL_PRESET => SysdColumn::Preset,
            COL_LOAD => SysdColumn::Load,
            COL_ACTIVE => SysdColumn::Active,
            COL_SUBSTATE => SysdColumn::SubState,
            COL_DESCRIPTION => SysdColumn::Description,
            TIMER_TIME_NEXT => SysdColumn::TimerTimeNextElapseRT,
            TIMER_TIME_LEFT => SysdColumn::TimerTimeLeftElapseMono,
            TIMER_TIME_PASSED => SysdColumn::TimerTimePassed,
            TIMER_TIME_LAST => SysdColumn::TimerTimeLast,
            SOCKET_LISTEN_COL => SysdColumn::SocketListen,
            SOCKET_LISTEN_TYPE => SysdColumn::SocketListenType,
            PATH_CONDITION_COL => SysdColumn::PathCondition,
            PATH_PATH_COL => SysdColumn::Path,
            AUTOMOUNT_MOUNTED_COL => SysdColumn::AutomountMounted,
            AUTOMOUNT_WHAT_COL => SysdColumn::AutomountWhat,
            AUTOMOUNT_IDLE_TIMEOUT_COL => SysdColumn::AutomountIdleTimeOut,
            _ => {
                if let Some((utype, _prop)) = id.split_once('@') {
                    let ut: UnitType = utype.into();
                    if UnitType::Unknown != ut {
                        if verify_prop_type(&prop_type) {
                            return Ok(Self::fill_custom2(ut, id, prop_type));
                        } else {
                            return Err((
                                SysdColumnNonConformity::CustomColIdWithoutUnitType,
                                defective_custom(id, prop_type),
                            ));
                        }
                    } else {
                        return Err((
                            SysdColumnNonConformity::CustomColWithoutDefinePropType,
                            defective_custom(id, prop_type),
                        ));
                    }
                } else {
                    error!("Unknown type for property : {:?}", id);
                    return Err((
                        SysdColumnNonConformity::CustomColWithoutDefinePropType,
                        defective_custom(id, prop_type),
                    ));
                }
            }
        };
        Ok(col)
    }

    pub fn id(&self) -> &str {
        match self {
            SysdColumn::Name => COL_ID_UNIT,
            SysdColumn::FullName => COL_ID_UNIT_FULL,
            SysdColumn::Bus => COL_BUS,
            SysdColumn::Type => COL_TYPE,
            SysdColumn::State => COL_STATE,
            SysdColumn::Preset => COL_PRESET,
            SysdColumn::Load => COL_LOAD,
            SysdColumn::Active => COL_ACTIVE,
            SysdColumn::SubState => COL_SUBSTATE,
            SysdColumn::Description => COL_DESCRIPTION,
            SysdColumn::TimerTimeNextElapseRT => TIMER_TIME_NEXT,
            SysdColumn::TimerTimeLeftElapseMono => TIMER_TIME_LEFT,
            SysdColumn::TimerTimePassed => TIMER_TIME_PASSED,
            SysdColumn::TimerTimeLast => TIMER_TIME_LAST,
            SysdColumn::SocketListen => SOCKET_LISTEN_COL,
            SysdColumn::SocketListenType => SOCKET_LISTEN_TYPE,
            SysdColumn::PathCondition => PATH_CONDITION_COL,
            SysdColumn::Path => PATH_PATH_COL,
            SysdColumn::AutomountWhat => AUTOMOUNT_WHAT_COL,
            SysdColumn::AutomountMounted => AUTOMOUNT_MOUNTED_COL,
            SysdColumn::AutomountIdleTimeOut => AUTOMOUNT_IDLE_TIMEOUT_COL,
            SysdColumn::Custom(c) => c.id.as_str(),
        }
    }

    pub fn property_type(&self) -> &Option<String> {
        match self {
            SysdColumn::Custom(c) => &c.signature,
            _ => &None,
        }
    }

    pub(crate) fn property(&self) -> &str {
        match self {
            SysdColumn::Name => "Name",
            SysdColumn::FullName => "Name",
            SysdColumn::Type => "Type",
            SysdColumn::Bus => "Bus",
            SysdColumn::State => "UnitFileState",
            SysdColumn::Preset => "UnitFilePreset",
            SysdColumn::Load => "LoadStat",
            SysdColumn::Active => "Active",
            SysdColumn::SubState => "SubState",
            SysdColumn::Description => "Description",
            SysdColumn::TimerTimePassed | SysdColumn::TimerTimeLast => TIME_LAST_TRIGGER_USEC,
            SysdColumn::TimerTimeNextElapseRT => TIME_NEXT_ELAPSE_USEC_REALTIME,
            SysdColumn::TimerTimeLeftElapseMono => TIME_NEXT_ELAPSE_USEC_MONOTONIC,
            SysdColumn::SocketListen | SysdColumn::SocketListenType => SOCKET_LISTEN,
            SysdColumn::PathCondition | SysdColumn::Path => PATH_PATHS,
            SysdColumn::AutomountMounted | SysdColumn::AutomountWhat => WHERE_PROP,
            SysdColumn::AutomountIdleTimeOut => AUTOMOUNT_IDLE_TIMEOUT_PROP,
            SysdColumn::Custom(c) => c.property(),
        }
    }

    pub(crate) fn generate_quark(&self) -> glib::Quark {
        let qstr = self.property();
        glib::Quark::from_str(qstr)
    }

    pub(crate) fn utype(&self) -> UnitType {
        match self {
            SysdColumn::TimerTimePassed
            | SysdColumn::TimerTimeLast
            | SysdColumn::TimerTimeNextElapseRT
            | SysdColumn::TimerTimeLeftElapseMono => UnitType::Timer,
            SysdColumn::SocketListen | SysdColumn::SocketListenType => UnitType::Socket,
            SysdColumn::PathCondition | SysdColumn::Path => UnitType::Path,
            SysdColumn::AutomountWhat
            | SysdColumn::AutomountMounted
            | SysdColumn::AutomountIdleTimeOut => UnitType::Automount,
            SysdColumn::Custom(c) => c.utype,
            _ => UnitType::Unknown,
        }
    }

    pub(crate) fn is_custom(&self) -> bool {
        matches!(self, SysdColumn::Custom(_,))
    }

    pub(crate) fn verify(
        unit_column_config: &UnitColumn,
    ) -> Result<SysdColumn, (SysdColumnNonConformity, SysdColumn)> {
        Self::new(
            unit_column_config.id.clone(),
            unit_column_config.prop_type.clone(),
        )
    }

    pub(crate) fn fill_custom(utype: UnitType, property: &str, property_type: &str) -> SysdColumn {
        let utype_str = utype.as_str();
        let id = format!("{}@{property}", utype_str);
        let c = CustomProp {
            utype,
            id,
            prop_idx: (utype_str.len() + 1) as u8,
            signature: Some(property_type.to_owned()),
        };
        SysdColumn::Custom(c)
    }

    fn fill_custom2(utype: UnitType, id: String, signature: Option<String>) -> SysdColumn {
        let utype_str = utype.as_str();
        let c = CustomProp {
            utype,
            id,
            prop_idx: (utype_str.len() + 1) as u8,
            signature,
        };
        SysdColumn::Custom(c)
    }

    pub(crate) fn specials() -> Vec<SysdColumn> {
        vec![
            SysdColumn::TimerTimeNextElapseRT,
            SysdColumn::TimerTimeLeftElapseMono,
            SysdColumn::TimerTimePassed,
            SysdColumn::TimerTimeLast,
            SysdColumn::SocketListen,
            SysdColumn::SocketListenType,
            SysdColumn::PathCondition,
            SysdColumn::Path,
            SysdColumn::AutomountWhat,
            SysdColumn::AutomountMounted,
            SysdColumn::AutomountIdleTimeOut,
        ]
    }
}

impl std::hash::Hash for SysdColumn {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        match &self {
            SysdColumn::SocketListen | SysdColumn::SocketListenType => {
                core::mem::discriminant(&SysdColumn::SocketListen).hash(state)
            }
            _ => core::mem::discriminant(self).hash(state),
        }
    }
}

impl PartialEq for SysdColumn {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (
                Self::SocketListen | Self::SocketListenType,
                Self::SocketListen | Self::SocketListenType,
            ) => true,
            (Self::Custom(l0), Self::Custom(r0)) => l0 == r0,
            _ => core::mem::discriminant(self) == core::mem::discriminant(other),
        }
    }
}

fn defective_custom(id: String, signature: Option<String>) -> SysdColumn {
    SysdColumn::Custom(CustomProp {
        utype: UnitType::Unknown,
        id,
        prop_idx: 0,
        signature,
    })
}

fn verify_prop_type(prop_type: &Option<String>) -> bool {
    prop_type.is_some()
}

impl From<(&str, Option<String>)> for SysdColumn {
    fn from(value: (&str, Option<String>)) -> Self {
        (value.0.to_owned(), value.1).into()
    }
}

impl From<(String, Option<String>)> for SysdColumn {
    fn from(value: (String, Option<String>)) -> Self {
        match SysdColumn::new(value.0, value.1) {
            Ok(c) => c,
            Err((_e, c)) => c,
        }
    }
}

impl From<(GString, Option<String>)> for SysdColumn {
    fn from(value: (GString, Option<String>)) -> Self {
        (value.0.as_str(), value.1).into()
    }
}

#[derive(Debug)]
pub enum SysdColumnNonConformity {
    CustomColWithoutDefinePropType,
    CustomColIdWithoutUnitType,
}