1use std::{cmp::Ordering, fmt::Debug};
2
3use crate::{
4 enums::{ActiveState, LoadState, Preset, UnitFileStatus},
5 sysdbus::ListedUnitFile,
6};
7
8use super::UpdatedUnitInfo;
9
10use base::enums::UnitDBusLevel;
11use glib::{self, object::ObjectExt, subclass::types::ObjectSubclassIsExt};
12
13use serde::Deserialize;
14use tracing::warn;
15use zvariant::{OwnedObjectPath, OwnedValue, Value};
16
17pub const SYSD_SOCKET_LISTEN_IDX: &str = "sysdSocketListenIdx";
18pub const PRIMARY: &str = "primary";
19glib::wrapper! {
20 pub struct UnitInfo(ObjectSubclass<imp::UnitInfoImpl>);
21}
22
23impl UnitInfo {
24 pub fn from_listed_unit(listed_unit: ListedLoadedUnit, level: UnitDBusLevel) -> Self {
25 let this_object: Self = glib::Object::builder()
27 .property(PRIMARY, &listed_unit.primary_unit_name)
28 .build();
29 this_object.init_from_listed_unit(listed_unit, level);
30 this_object
31 }
32
33 pub fn init_from_listed_unit(&self, listed_unit: ListedLoadedUnit, level: UnitDBusLevel) {
34 let imp = self.imp();
35 imp.init_from_listed_unit(listed_unit, level);
36 }
37
38 pub fn from_unit_file(unit_file: ListedUnitFile, level: UnitDBusLevel) -> Self {
39 let this_object: Self = glib::Object::builder()
41 .property(PRIMARY, unit_file.unit_primary_name())
42 .build();
43 this_object.imp().init_from_unit_file(unit_file, level);
44 this_object
45 }
46
47 pub fn update_from_loaded_unit(&self, listed_unit: ListedLoadedUnit) {
48 self.imp().update_from_listed_unit(listed_unit);
49 }
50
51 pub fn update_from_unit_info(&self, update: UpdatedUnitInfo) {
52 self.imp().update_from_unit_info(self, update);
53 }
54
55 pub fn update_from_unit_file(&self, unit_file: ListedUnitFile) {
56 self.imp().update_from_unit_file(unit_file);
57 }
58
59 pub fn debug(&self) -> String {
60 format!("{:#?}", *self.imp())
61 }
62
63 pub fn need_to_be_completed(&self) -> bool {
64 self.imp().need_to_be_completed()
65 }
66
67 pub fn insert_socket_listen(&self, quark: glib::Quark, value: OwnedValue) -> usize {
68 let mut alen = 0;
69 if let Value::Array(array) = &value as &Value {
70 let mut new_array = Vec::with_capacity(array.len());
71
72 for s in array.iter() {
73 if let Value::Structure(structure) = s {
74 let f = structure.fields();
75 if f.len() == 2 {
76 let f0: String = f[0]
77 .clone()
78 .try_into()
79 .inspect_err(|err| warn!("socket listen {err:?}"))
80 .unwrap_or_default();
81 let f1: String = f[1]
82 .clone()
83 .try_into()
84 .inspect_err(|err| warn!("socket listen {err:?}"))
85 .unwrap_or_default();
86 new_array.push((f0, f1));
87 }
88 }
89 }
90
91 alen = new_array.len();
92 unsafe { self.set_qdata(quark, new_array) };
93 }
94 alen
95 }
96
97 pub fn insert_unit_property_value(&self, quark: glib::Quark, value: OwnedValue) {
98 match &value as &Value {
100 Value::Bool(b) => unsafe { self.set_qdata(quark, *b) },
101 Value::U8(i) => unsafe { self.set_qdata(quark, *i) },
102 Value::I16(i) => unsafe { self.set_qdata(quark, *i) },
103 Value::U16(i) => unsafe { self.set_qdata(quark, *i) },
104 Value::I32(i) => unsafe { self.set_qdata(quark, *i) },
105 Value::U32(i) => unsafe { self.set_qdata(quark, *i) },
106 Value::I64(i) => unsafe { self.set_qdata(quark, *i) },
107 Value::U64(i) => unsafe { self.set_qdata(quark, *i) },
108 Value::F64(i) => unsafe { self.set_qdata(quark, *i) },
109 Value::Str(s) => {
110 if s.is_empty() {
111 unsafe { self.steal_qdata::<String>(quark) };
112 } else {
113 unsafe { self.set_qdata(quark, s.to_string()) };
114 }
115 }
116 Value::Signature(s) => unsafe { self.set_qdata(quark, s.to_string()) },
117 Value::ObjectPath(op) => unsafe { self.set_qdata(quark, op.to_string()) },
118 Value::Value(val) => unsafe { self.set_qdata(quark, val.to_string()) },
119 Value::Array(array) => {
120 if array.is_empty() {
121 unsafe { self.steal_qdata::<String>(quark) };
122 } else {
123 let mut d_str = String::from("");
124
125 let mut it = array.iter().peekable();
126 while let Some(mi) = it.next() {
127 if let Some(str_value) = convert_to_string(mi) {
128 d_str.push_str(&str_value);
129 }
130 if it.peek().is_some() {
131 d_str.push_str(", ");
132 }
133 }
134
135 unsafe { self.set_qdata(quark, d_str) };
136 }
137 }
138 Value::Dict(d) => {
139 let mut it = d.iter().peekable();
140 if it.peek().is_none() {
141 unsafe { self.steal_qdata::<String>(quark) };
142 } else {
143 let mut d_str = String::from("{ ");
144
145 for (mik, miv) in it {
146 if let Some(k) = convert_to_string(mik) {
147 d_str.push_str(&k);
148 }
149 d_str.push_str(" : ");
150
151 if let Some(v) = convert_to_string(miv) {
152 d_str.push_str(&v);
153 }
154 }
155 d_str.push_str(" }");
156
157 unsafe { self.set_qdata(quark, d_str) };
158 }
159 }
160 Value::Structure(stc) => {
161 let mut it = stc.fields().iter().peekable();
162
163 if it.peek().is_none() {
164 unsafe { self.steal_qdata::<String>(quark) };
165 } else {
166 let v: Vec<String> = it
167 .filter_map(|v| convert_to_string(v))
168 .filter(|s| !s.is_empty())
169 .collect();
170 let d_str = v.join(", ");
171
172 unsafe { self.set_qdata(quark, d_str) };
173 }
174 }
175 Value::Fd(fd) => unsafe { self.set_qdata(quark, fd.to_string()) },
176 }
178 }
179
180 pub fn get_custom_property_to_string<T>(&self, key: glib::Quark) -> Option<String>
181 where
182 T: ToString + 'static,
183 {
184 unsafe { self.qdata::<T>(key) }
185 .map(|value_ptr| unsafe { value_ptr.as_ref() })
186 .map(|value| value.to_string())
187 }
188
189 pub fn get_custom_property<T: 'static>(&self, key: glib::Quark) -> Option<&T> {
190 unsafe { self.qdata::<T>(key) }.map(|value_ptr| unsafe { value_ptr.as_ref() })
191 }
192
193 pub fn display_custom_property(&self, key: glib::Quark) -> Option<String> {
194 unsafe { self.qdata::<OwnedValue>(key) }
195 .map(|value_ptr| unsafe { value_ptr.as_ref() })
196 .and_then(|value| convert_to_string(value))
197 }
198
199 pub fn is_template_unit_file(&self) -> bool {
200 self.imp().is_template_unit_file()
201 }
202}
203
204pub fn get_custom_property_typed_raw<T, O>(unit: &O, key: glib::Quark) -> Option<T>
205where
206 T: Copy + 'static,
207 O: ObjectExt,
208{
209 unsafe { unit.qdata::<T>(key) }
210 .map(|value_ptr| unsafe { value_ptr.as_ref() })
211 .copied()
212}
213
214mod imp {
215 use std::{
216 cell::{Cell, OnceCell, RefCell},
217 str::FromStr,
218 };
219
220 use base::enums::UnitDBusLevel;
221 use glib::{
222 self,
223 object::ObjectExt,
224 subclass::{object::*, types::ObjectSubclass},
225 };
226
227 use crate::{
228 UpdatedUnitInfo,
229 data::ListedLoadedUnit,
230 enums::{ActiveState, LoadState, Preset, UnitFileStatus, UnitType},
231 sysdbus::ListedUnitFile,
232 };
233
234 #[derive(Debug, glib::Properties, Default)]
235 #[properties(wrapper_type = super::UnitInfo)]
236 pub struct UnitInfoImpl {
237 #[property(get, construct_only, set = Self::set_primary)]
238 pub(super) primary: OnceCell<String>,
239
240 #[property(get = Self::get_display_name, type = String)]
241 display_name: OnceCell<u32>,
242
243 #[property(get, default)]
244 unit_type: Cell<UnitType>,
245
246 #[property(get, set)]
247 pub(super) description: RefCell<Option<String>>,
248
249 #[property(get, set, default)]
250 pub(super) load_state: Cell<LoadState>,
251
252 #[property(get, set, builder(ActiveState::Unknown))]
253 pub(super) active_state: Cell<ActiveState>,
254
255 #[property(get, set)]
256 pub(super) sub_state: RefCell<String>,
257
258 #[property(get)]
259 pub(super) followed_unit: RefCell<String>,
260
261 #[property(get=Self::get_unit_path, type = String)]
263 pub(super) object_path: OnceCell<String>,
264 #[property(get, set, nullable, default = None)]
265 pub(super) file_path: RefCell<Option<String>>,
266 #[property(get, set, default)]
267 pub(super) enable_status: Cell<UnitFileStatus>,
268
269 #[property(get, set, default)]
270 pub(super) dbus_level: Cell<UnitDBusLevel>,
271
272 #[property(get, set, default)]
273 pub(super) preset: Cell<Preset>,
274 }
275
276 #[glib::object_subclass]
277 impl ObjectSubclass for UnitInfoImpl {
278 const NAME: &'static str = "UnitInfo";
279 type Type = super::UnitInfo;
280 type ParentType = glib::Object;
281 fn new() -> Self {
282 Default::default()
283 }
284 }
285
286 #[glib::derived_properties]
287 impl ObjectImpl for UnitInfoImpl {}
288
289 impl UnitInfoImpl {
290 pub(super) fn init_from_listed_unit(
291 &self,
292 listed_unit: super::ListedLoadedUnit,
293 dbus_level: UnitDBusLevel,
294 ) {
295 self.dbus_level.replace(dbus_level);
296 self.update_from_listed_unit(listed_unit);
297 }
298
299 pub(super) fn update_from_listed_unit(&self, listed_unit: ListedLoadedUnit) {
300 let active_state: ActiveState = listed_unit.active_state.as_str().into();
301
302 self.active_state.replace(active_state);
304
305 let description = if listed_unit.description.is_empty() {
306 None
307 } else {
308 Some(listed_unit.description)
309 };
310
311 self.description.replace(description);
312 let load_state: LoadState = listed_unit.load_state.as_str().into();
313 self.load_state.replace(load_state);
314 self.sub_state.replace(listed_unit.sub_state);
315 self.followed_unit.replace(listed_unit.followed_unit);
316 }
317
318 pub(super) fn init_from_unit_file(&self, unit_file: ListedUnitFile, level: UnitDBusLevel) {
319 self.dbus_level.replace(level);
320 self.update_from_unit_file(unit_file)
321 }
322
323 pub(super) fn update_from_unit_file(&self, unit_file: ListedUnitFile) {
324 self.file_path.replace(Some(unit_file.unit_file_path));
325 let status = UnitFileStatus::from_str(&unit_file.enablement_status)
326 .unwrap_or(UnitFileStatus::default());
327 self.enable_status.replace(status);
328 }
329
330 fn set_primary(&self, primary: String) {
331 let mut split_char_index = primary.len();
332 for (i, c) in primary.chars().rev().enumerate() {
333 if c == '.' {
334 split_char_index -= i;
335 break;
336 }
337 }
338
339 self.display_name.set((split_char_index - 1) as u32);
341
342 let unit_type = UnitType::new(&primary[(split_char_index)..]);
343 self.unit_type.set(unit_type);
344
345 self.primary.set(primary);
346 }
347
348 pub fn get_display_name(&self) -> String {
349 let index = *self.display_name.get_or_init(|| unreachable!()) as usize;
350 let s = &self.primary.get().expect("Being set")[..index];
351 s.to_owned()
352 }
353
354 pub fn update_from_unit_info(&self, unit: &super::UnitInfo, update: UpdatedUnitInfo) {
355 self.description.replace(update.description);
358
359 if let Some(sub_state) = update.sub_state {
360 self.sub_state.replace(sub_state);
361 }
362
363 if let Some(active_state) = update.active_state {
364 self.active_state.replace(active_state);
365 }
366
367 if let Some(unit_file_preset) = update.unit_file_preset {
368 let preset: Preset = unit_file_preset.into();
369 unit.set_preset(preset);
370 }
371
372 if let Some(load_state) = update.load_state {
373 unit.set_load_state(load_state);
374 }
375
376 if let Some(fragment_path) = update.fragment_path {
377 self.file_path.replace(Some(fragment_path));
378 }
379
380 if let Some(enablement_status) = update.enablement_status {
381 self.enable_status.replace(enablement_status);
382 }
383 }
384
385 fn get_unit_path(&self) -> String {
386 let object_path = self.object_path.get_or_init(|| {
387 let primary = self.primary.get_or_init(|| unreachable!());
388 crate::sysdbus::unit_dbus_path_from_name(primary)
389 });
390 object_path.clone()
391 }
392
393 pub fn need_to_be_completed(&self) -> bool {
394 self.description.borrow().is_none() || self.preset.get() == Preset::UnSet
395 }
397
398 pub(crate) fn is_template_unit_file(&self) -> bool {
399 let index = *self
400 .display_name
401 .get_or_init(|| unreachable!("Has to be set")) as usize;
402 let s = &self.primary.get().expect("Being set")[..index];
403 s.ends_with('@')
404 }
405 }
406}
407
408#[derive(Debug, Eq, PartialEq)]
409pub struct UnitProcess {
410 pub path: String,
411 pub pid: u32,
412 pub name: String,
413 pub(crate) unit_name: usize,
414}
415
416impl UnitProcess {
417 pub fn unit_name(&self) -> &str {
418 &self.path[self.unit_name..]
419 }
420}
421
422impl Ord for UnitProcess {
423 fn cmp(&self, other: &Self) -> Ordering {
424 let cmp: Ordering = self.unit_name().cmp(other.unit_name());
425 if self.unit_name().cmp(other.unit_name()) == Ordering::Equal {
426 self.pid.cmp(&other.pid)
427 } else {
428 cmp
429 }
430 }
431}
432
433impl PartialOrd for UnitProcess {
434 fn partial_cmp(&self, other: &UnitProcess) -> Option<Ordering> {
435 Some(self.cmp(other))
436 }
437}
438
439#[derive(Deserialize, zvariant::Type, PartialEq, Debug)]
440pub struct ListedLoadedUnit {
441 pub primary_unit_name: String,
442 pub description: String,
443 pub load_state: String,
444 pub active_state: String,
445 pub sub_state: String,
446 pub followed_unit: String,
447
448 pub unit_object_path: OwnedObjectPath,
449 pub numeric_job_id: u32,
451 pub job_type: String,
452 pub job_object_path: OwnedObjectPath,
453}
454
455pub fn convert_to_string(value: &Value) -> Option<String> {
456 match value {
457 Value::Bool(b) => Some(b.to_string()),
458 Value::U8(i) => Some(i.to_string()),
459 Value::I16(i) => Some(i.to_string()),
460 Value::U16(i) => Some(i.to_string()),
461 Value::I32(i) => Some(i.to_string()),
462 Value::U32(i) => Some(i.to_string()),
463 Value::I64(i) => Some(i.to_string()),
464 Value::U64(i) => Some(i.to_string()),
465 Value::F64(i) => Some(i.to_string()),
466 Value::Str(s) => Some(s.to_string()),
467 Value::Signature(s) => Some(s.to_string()),
468 Value::ObjectPath(op) => Some(op.to_string()),
469 Value::Value(v) => Some(v.to_string()),
470 Value::Array(a) => {
471 if a.is_empty() {
472 None
473 } else {
474 let mut d_str = String::from("");
475
476 let mut it = a.iter().peekable();
477 while let Some(mi) = it.next() {
478 if let Some(v) = convert_to_string(mi) {
479 d_str.push_str(&v);
480 }
481 if it.peek().is_some() {
482 d_str.push_str(", ");
483 }
484 }
485
486 Some(d_str)
487 }
488 }
489 Value::Dict(d) => {
490 let mut it = d.iter().peekable();
491 if it.peek().is_none() {
492 None
493 } else {
494 let mut d_str = String::from("{ ");
495
496 for (mik, miv) in it {
497 if let Some(k) = convert_to_string(mik) {
498 d_str.push_str(&k);
499 }
500 d_str.push_str(" : ");
501
502 if let Some(v) = convert_to_string(miv) {
503 d_str.push_str(&v);
504 }
505 }
506 d_str.push_str(" }");
507 Some(d_str)
508 }
509 }
510 Value::Structure(stc) => {
511 let mut it = stc.fields().iter().peekable();
512
513 if it.peek().is_none() {
514 None
515 } else {
516 let mut d_str = String::from("");
517
518 while let Some(mi) = it.next() {
519 if let Some(v) = convert_to_string(mi) {
520 d_str.push_str(&v);
521 }
522
523 if it.peek().is_some() {
524 d_str.push_str(", ");
525 }
526 }
527
528 Some(d_str)
529 }
530 }
531 Value::Fd(fd) => Some(fd.to_string()),
532 }
534}
535
536#[derive(Debug)]
537pub enum UnitPropertySetter {
538 FileState(UnitFileStatus),
539 Description(String),
540 ActiveState(ActiveState),
541 LoadState(LoadState),
542 FragmentPath(String),
543 UnitFilePreset(Preset),
544 SubState(String),
545 Custom(glib::Quark, OwnedValue),
546}