1use crate::agent::LaunchAgent;
2use crate::os::{get_user_id, run_shell};
3use crate::LaunchctlResult;
4
5pub trait LaunchControllable {
7 fn bootstrap(&self) -> LaunchctlResult<()>;
9
10 fn boot_out(&self) -> LaunchctlResult<()>;
12
13 fn is_running(&self) -> LaunchctlResult<bool>;
15}
16
17impl LaunchAgent {
18 fn format_command(&self, command: &str) -> String {
21 if command.is_empty() {
22 return String::new();
23 }
24 format!(
25 "launchctl {} gui/{} '{}'",
26 command,
27 get_user_id(),
28 self.path().display()
29 )
30 }
31
32 fn format_bootstrap_command(&self) -> String {
33 self.format_command("bootstrap")
34 }
35
36 fn format_boot_out_command(&self) -> String {
37 self.format_command("bootout")
38 }
39
40 fn format_print_command(&self) -> String {
41 format!("launchctl print gui/{}/{}", get_user_id(), self.label)
42 }
43
44 fn check_is_running(output: &str) -> bool {
46 output.contains("state = running")
47 }
48}
49
50impl LaunchControllable for LaunchAgent {
51 fn bootstrap(&self) -> LaunchctlResult<()> {
53 let cmd = self.format_bootstrap_command();
54 run_shell(&cmd).map(|_| ())
55 }
56
57 fn boot_out(&self) -> LaunchctlResult<()> {
60 let cmd = self.format_boot_out_command();
61 run_shell(&cmd).map(|_| ())
62 }
63
64 fn is_running(&self) -> LaunchctlResult<bool> {
66 let cmd = self.format_print_command();
67
68 let output = run_shell(&cmd)?;
69 Ok(LaunchAgent::check_is_running(&output))
70 }
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76
77 #[test]
78 fn test_format_command() {
79 let agent = LaunchAgent::new("test");
80 let agent_path = agent.path().display().to_string();
81 let user_id = get_user_id();
82
83 assert_eq!(
84 agent.format_command("subcommand"),
85 format!("launchctl subcommand gui/{user_id} '{agent_path}'")
86 );
87 assert_eq!(
88 agent.format_command("manageruid"),
89 format!("launchctl manageruid gui/{user_id} '{agent_path}'")
90 );
91 assert_eq!(agent.format_command(""), "");
92 }
93
94 #[test]
95 fn test_format_bootstrap_command() {
96 let agent = LaunchAgent::new("test");
97 let user_id = get_user_id();
98 let agent_path = agent.path().display().to_string();
99
100 assert_eq!(
101 agent.format_bootstrap_command(),
102 format!("launchctl bootstrap gui/{user_id} '{agent_path}'")
103 );
104 }
105
106 #[test]
107 fn test_format_bootout_command() {
108 let agent = LaunchAgent::new("test");
109 let user_id = get_user_id();
110 let agent_path = agent.path().display().to_string();
111
112 assert_eq!(
113 agent.format_boot_out_command(),
114 format!("launchctl bootout gui/{user_id} '{agent_path}'")
115 );
116 }
117
118 #[test]
119 fn test_check_info_command() {
120 let agent = LaunchAgent::new("test");
121 let user_id = get_user_id();
122
123 assert_eq!(
124 agent.format_print_command(),
125 format!("launchctl print gui/{user_id}/test")
126 );
127 }
128
129 #[test]
130 fn test_check_is_running() {
131 let output = "
132{
133 domain = gui/501 [100003]
134 asid = 100003
135
136 jetsam memory limit (active) = (unlimited)
137 jetsam memory limit (inactive) = (unlimited)
138 jetsamproperties category = daemon
139 jetsam thread limit = 32
140 cpumon = default
141 job state = running
142 probabilistic guard malloc policy = {
143 activation rate = 1/1000
144 sample rate = 1/0
145 }
146
147 properties = keepalive | runatload | inferred program | managed LWCR | has LWCR
148}
149 ";
150 assert!(LaunchAgent::check_is_running(output));
151
152 let output = "
153 {
154 domain = gui/501 [100003]
155 asid = 100003
156 }
157 ";
158 assert!(!LaunchAgent::check_is_running(output));
159 }
160}