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