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