primitive 0.2.9__py3-none-any.whl → 0.2.11__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,102 +1,130 @@
1
1
  import os
2
2
  from pathlib import Path
3
3
  import subprocess
4
+ from loguru import logger
5
+ from ..utils.daemons import Daemon
4
6
 
5
7
  HOME_DIRECTORY = Path.home()
6
8
  CURRENT_USER = str(HOME_DIRECTORY.expanduser()).lstrip("/Users/")
7
-
8
9
  PRIMITIVE_BINARY_PATH = Path(HOME_DIRECTORY / ".pyenv" / "shims" / "primitive")
9
10
 
10
- PRIMITIVE_LAUNCH_AGENT_FILEPATH = Path(
11
- HOME_DIRECTORY / "Library" / "LaunchAgents" / "tech.primitive.agent.plist"
12
- )
13
- PRIMITIVE_LAUNCH_AGENT_LOGS = Path(
14
- HOME_DIRECTORY / "Library" / "Logs" / "tech.primitive.agent.log"
15
- )
16
- PRIMITIVE_LAUNCH_AGENT_LABEL = "tech.primitive.agent"
17
- PRIMITIVE_LAUNCH_AGENT_WORKING_DIR = Path(
18
- HOME_DIRECTORY / "Logs" / "tech.primitive.agent.log"
19
- )
20
-
21
-
22
- def stop_launch_agent():
23
- try:
24
- stop_existing_process = f"launchctl stop {PRIMITIVE_LAUNCH_AGENT_LABEL}"
25
- subprocess.check_output(stop_existing_process.split(" "))
26
- return True
27
- except subprocess.CalledProcessError as exception:
28
- print("stop_launch_agent: ", exception)
29
- return False
30
-
31
-
32
- def start_launch_agent():
33
- try:
34
- start_new_agent = f"launchctl start {PRIMITIVE_LAUNCH_AGENT_LABEL}"
35
- subprocess.check_output(start_new_agent.split(" "))
36
- return True
37
- except subprocess.CalledProcessError as exception:
38
- print("start_launch_agent: ", exception)
39
- return False
40
-
41
-
42
- def unload_launch_agent():
43
- try:
44
- remove_existing_agent = f"launchctl unload -w {PRIMITIVE_LAUNCH_AGENT_FILEPATH}"
45
- subprocess.check_output(remove_existing_agent.split(" "))
46
- return True
47
- except subprocess.CalledProcessError as exception:
48
- print("remove_launch_agent: ", exception)
49
- return False
50
-
51
-
52
- def load_launch_agent():
53
- try:
54
- load_new_plist = f"launchctl load -w {PRIMITIVE_LAUNCH_AGENT_FILEPATH}"
55
- subprocess.check_output(load_new_plist.split(" "))
56
- return True
57
- except subprocess.CalledProcessError as exception:
58
- print("load_launch_agent: ", exception)
59
- return False
60
-
61
-
62
- def create_stdout_file():
63
- if not PRIMITIVE_LAUNCH_AGENT_LOGS.exists():
64
- PRIMITIVE_LAUNCH_AGENT_LOGS.parent.mkdir(parents=True, exist_ok=True)
65
- PRIMITIVE_LAUNCH_AGENT_LOGS.touch()
66
-
67
-
68
- def delete_stdout_file():
69
- if PRIMITIVE_LAUNCH_AGENT_LOGS.exists():
70
- PRIMITIVE_LAUNCH_AGENT_LOGS.unlink()
71
-
72
-
73
- def populate_fresh_launch_agent():
74
- PRIMITIVE_LAUNCH_AGENT_LOGS.parent.mkdir(parents=True, exist_ok=True)
75
- PRIMITIVE_LAUNCH_AGENT_LOGS.touch()
76
-
77
- if PRIMITIVE_LAUNCH_AGENT_FILEPATH.exists():
78
- PRIMITIVE_LAUNCH_AGENT_FILEPATH.unlink()
79
- PRIMITIVE_LAUNCH_AGENT_FILEPATH.parent.mkdir(parents=True, exist_ok=True)
80
- PRIMITIVE_LAUNCH_AGENT_FILEPATH.touch()
81
-
82
- found_primitive_binary_path = PRIMITIVE_BINARY_PATH
83
- if not PRIMITIVE_BINARY_PATH.exists():
84
- result = subprocess.run(["which", "primitive"], capture_output=True)
85
- if result.returncode == 0:
86
- found_primitive_binary_path = result.stdout.decode().rstrip("\n")
87
- else:
88
- print("primitive binary not found")
11
+
12
+ class LaunchAgent(Daemon):
13
+ def __init__(self, label: str):
14
+ self.label = label
15
+ self.name = label.split(".")[-1]
16
+
17
+ @property
18
+ def file_path(self) -> Path:
19
+ return Path(HOME_DIRECTORY / "Library" / "LaunchAgents" / f"{self.label}.plist")
20
+
21
+ @property
22
+ def logs(self) -> Path:
23
+ return Path(HOME_DIRECTORY / "Library" / "Logs" / f"{self.label}.log")
24
+
25
+ @property
26
+ def cmd(self) -> str:
27
+ return self.label.split(".")[-1]
28
+
29
+ def stop(self, unload: bool = True) -> bool:
30
+ try:
31
+ stop_existing_process = f"launchctl stop {self.label}"
32
+ subprocess.check_output(
33
+ stop_existing_process.split(" "), stderr=subprocess.DEVNULL
34
+ )
35
+ logger.info(f":white_check_mark: {self.label} stopped successfully!")
36
+ if unload:
37
+ self.unload() # Need to unload with KeepAlive = true or else launchctl will try to pick it up again
38
+ return True
39
+ except subprocess.CalledProcessError as exception:
40
+ if exception.returncode == 3:
41
+ logger.debug(f"{self.label} is not running or does not exist.")
42
+ return True
43
+ else:
44
+ logger.error(f"Unable to stop {self.label}, {exception.returncode}")
45
+ logger.error(exception)
46
+ return False
47
+
48
+ def start(self, load: bool = True) -> bool:
49
+ if load:
50
+ self.load()
51
+ try:
52
+ start_new_agent = f"launchctl start {self.label}"
53
+ subprocess.check_output(
54
+ start_new_agent.split(" "), stderr=subprocess.DEVNULL
55
+ )
56
+ logger.info(f":white_check_mark: {self.label} started successfully!")
57
+ return True
58
+ except subprocess.CalledProcessError as exception:
59
+ logger.error(f"Unable to start {self.label}")
60
+ logger.error(exception)
61
+ return False
62
+
63
+ def unload(self) -> bool:
64
+ try:
65
+ remove_existing_agent = f"launchctl unload -w {self.file_path}"
66
+ subprocess.check_output(
67
+ remove_existing_agent.split(" "), stderr=subprocess.DEVNULL
68
+ )
69
+ return True
70
+ except subprocess.CalledProcessError as exception:
71
+ logger.error(f"Unable to unload {self.label}")
72
+ logger.error(exception)
73
+ return False
74
+
75
+ def load(self) -> bool:
76
+ try:
77
+ load_new_plist = f"launchctl load -w {self.file_path}"
78
+ subprocess.check_output(
79
+ load_new_plist.split(" "), stderr=subprocess.DEVNULL
80
+ )
81
+ return True
82
+ except subprocess.CalledProcessError as exception:
83
+ logger.error(f"Unable to load {self.label}")
84
+ logger.error(exception)
85
+ return False
86
+
87
+ def verify(self) -> bool:
88
+ plutil_check = f"plutil -lint {self.file_path}"
89
+ try:
90
+ subprocess.check_output(plutil_check.split(" "), stderr=subprocess.DEVNULL)
91
+ return True
92
+ except subprocess.CalledProcessError as exception:
93
+ logger.error(f"Unable to verify {self.label}")
94
+ logger.error(exception)
89
95
  return False
90
96
 
91
- PRIMITIVE_LAUNCH_AGENT_FILEPATH.write_text(
92
- f"""<?xml version="1.0" encoding="UTF-8"?>
97
+ def view_logs(self) -> None:
98
+ follow_logs = f"tail -f -n +1 {self.logs}"
99
+ os.system(follow_logs)
100
+
101
+ def populate(self) -> bool:
102
+ self.logs.parent.mkdir(parents=True, exist_ok=True)
103
+ self.logs.touch()
104
+
105
+ if self.file_path.exists():
106
+ self.file_path.unlink()
107
+ self.file_path.parent.mkdir(parents=True, exist_ok=True)
108
+ self.file_path.touch()
109
+
110
+ found_primitive_binary_path = PRIMITIVE_BINARY_PATH
111
+ if not PRIMITIVE_BINARY_PATH.exists():
112
+ result = subprocess.run(["which", "primitive"], capture_output=True)
113
+ if result.returncode == 0:
114
+ found_primitive_binary_path = result.stdout.decode().rstrip("\n")
115
+ else:
116
+ logger.error("primitive binary not found")
117
+ return False
118
+
119
+ self.file_path.write_text(
120
+ f"""<?xml version="1.0" encoding="UTF-8"?>
93
121
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
94
122
  <plist version="1.0">
95
123
  <dict>
96
124
  <key>KeepAlive</key>
97
125
  <true/>
98
126
  <key>Label</key>
99
- <string>{PRIMITIVE_LAUNCH_AGENT_LABEL}</string>
127
+ <string>{self.label}</string>
100
128
  <key>LimitLoadToSessionType</key>
101
129
  <array>
102
130
  <string>Aqua</string>
@@ -107,48 +135,103 @@ def populate_fresh_launch_agent():
107
135
  <key>ProgramArguments</key>
108
136
  <array>
109
137
  <string>{found_primitive_binary_path}</string>
110
- <string>agent</string>
138
+ <string>{self.cmd}</string>
111
139
  </array>
112
140
  <key>RunAtLoad</key>
113
141
  <true/>
114
142
  <key>StandardErrorPath</key>
115
- <string>{PRIMITIVE_LAUNCH_AGENT_LOGS}</string>
143
+ <string>{self.logs}</string>
116
144
  <key>StandardOutPath</key>
117
- <string>{PRIMITIVE_LAUNCH_AGENT_LOGS}</string>
145
+ <string>{self.logs}</string>
118
146
  </dict>
119
- </plist>""" # noqa: E501
120
- )
121
- PRIMITIVE_LAUNCH_AGENT_FILEPATH.chmod(0o644)
122
- verify_launch_agent()
123
-
124
-
125
- def verify_launch_agent():
126
- plutil_check = f"plutil -lint {PRIMITIVE_LAUNCH_AGENT_FILEPATH}"
127
- try:
128
- subprocess.check_output(plutil_check.split(" "))
129
- return True
130
- except subprocess.CalledProcessError as exception:
131
- print("verify_launch_agent: ", exception)
132
- return False
133
-
134
-
135
- def view_launch_agent_logs():
136
- follow_logs = f"tail -f -n +1 {PRIMITIVE_LAUNCH_AGENT_LOGS}"
137
- os.system(follow_logs)
138
-
139
-
140
- def full_launch_agent_install():
141
- stop_launch_agent()
142
- unload_launch_agent()
143
- populate_fresh_launch_agent()
144
- create_stdout_file()
145
- load_launch_agent()
146
- start_launch_agent()
147
-
148
-
149
- def full_launch_agent_uninstall():
150
- stop_launch_agent()
151
- unload_launch_agent()
152
- if PRIMITIVE_LAUNCH_AGENT_FILEPATH.exists():
153
- PRIMITIVE_LAUNCH_AGENT_FILEPATH.unlink()
154
- delete_stdout_file()
147
+ </plist>"""
148
+ )
149
+ self.file_path.chmod(0o644)
150
+ return self.verify()
151
+
152
+ def create_stdout_file(self) -> bool:
153
+ try:
154
+ if not self.logs.exists():
155
+ self.logs.parent.mkdir(parents=True, exist_ok=True)
156
+ self.logs.touch()
157
+ return True
158
+ except Exception as e:
159
+ logger.error(
160
+ f"Unable to create log file at {self.logs} for daemon {self.label}"
161
+ )
162
+ logger.error(e)
163
+ return False
164
+
165
+ def delete_stdout_file(self) -> bool:
166
+ try:
167
+ if self.logs.exists():
168
+ self.logs.unlink()
169
+ return True
170
+ except Exception as e:
171
+ logger.error(
172
+ f"Unable to delete log file at {self.logs} for daemon {self.label}"
173
+ )
174
+ logger.error(e)
175
+ return False
176
+
177
+ def delete_plist_file(self) -> bool:
178
+ try:
179
+ if self.file_path.exists():
180
+ self.file_path.unlink()
181
+ return True
182
+ except Exception as e:
183
+ logger.error(
184
+ f"Unable to delete log file at {self.logs} for daemon {self.label}"
185
+ )
186
+ logger.error(e)
187
+ return False
188
+
189
+ def install(self) -> bool:
190
+ return all(
191
+ [
192
+ self.stop(),
193
+ self.unload(),
194
+ self.populate(),
195
+ self.create_stdout_file(),
196
+ self.load(),
197
+ self.start(load=False),
198
+ ]
199
+ )
200
+
201
+ def uninstall(self) -> bool:
202
+ return all(
203
+ [
204
+ self.stop(unload=False),
205
+ self.unload(),
206
+ self.delete_plist_file(),
207
+ self.delete_stdout_file(),
208
+ ]
209
+ )
210
+
211
+ def is_active(self) -> bool:
212
+ if not self.is_installed():
213
+ return False
214
+
215
+ try:
216
+ is_service_active = f"launchctl list {self.label}" # noqa
217
+ output = (
218
+ subprocess.check_output(is_service_active.split(" ")).decode().strip()
219
+ )
220
+ return "PID" in output
221
+ except subprocess.CalledProcessError as exception:
222
+ logger.error(f"Unable to check if {self.label} active")
223
+ logger.error(exception)
224
+ return False
225
+
226
+ def is_installed(self) -> bool:
227
+ try:
228
+ is_service_active = f"launchctl list {self.label}" # noqa
229
+ subprocess.check_output(
230
+ is_service_active.split(" "), stderr=subprocess.DEVNULL
231
+ )
232
+ return True
233
+ except subprocess.CalledProcessError as exception:
234
+ if exception.returncode != 113:
235
+ logger.error(f"Unable to check if {self.label} enabled")
236
+ logger.error(exception)
237
+ return False