ViewVC Help
View File | Revision Log | Show Annotations | View Changeset | Root Listing
root/salaryman/trunk/src/service.rs
Revision: 17
Committed: Fri Aug 1 08:48:17 2025 UTC (2 months, 1 week ago) by yuzu
File size: 10089 byte(s)
Log Message:
unix socket get

File Contents

# Content
1 use serde::{Deserialize, Serialize};
2 use std::{
3 fs::File,
4 io::{BufRead, BufReader, Write},
5 path::PathBuf,
6 process::{Child, Command, Stdio},
7 };
8 use uuid::Uuid;
9
10 pub enum ServiceState {
11 Running,
12 Failed,
13 Stopped,
14 }
15
16 #[derive(Serialize, Deserialize, Clone, Debug)]
17 pub struct ServiceConf {
18 uuid: Uuid,
19 name: String,
20 command: String,
21 args: Option<String>,
22 directory: Option<PathBuf>,
23 autostart: bool,
24 oneshot: Option<bool>,
25 }
26 impl Default for ServiceConf {
27 fn default() -> Self {
28 Self::new()
29 }
30 }
31 impl ServiceConf {
32 /// Returns a new empty `ServiceConf`
33 pub fn new() -> Self {
34 Self {
35 uuid: Uuid::new_v4(),
36 name: String::new(),
37 command: String::new(),
38 args: None,
39 directory: None,
40 autostart: false,
41 oneshot: None,
42 }
43 }
44 /// Returns a new `ServiceConf` from parts.
45 pub fn from_parts(
46 uuid: Uuid,
47 name: String,
48 command: String,
49 args: Option<String>,
50 directory: Option<PathBuf>,
51 autostart: bool,
52 oneshot: Option<bool>,
53 ) -> Self {
54 Self {
55 uuid,
56 name,
57 command,
58 args,
59 directory,
60 autostart,
61 oneshot,
62 }
63 }
64 /// Returns a new `ServiceConf` from parts with new uuid.
65 pub fn new_from_parts(
66 name: String,
67 command: String,
68 args: Option<String>,
69 directory: Option<PathBuf>,
70 autostart: bool,
71 oneshot: Option<bool>,
72 ) -> Self {
73 Self {
74 uuid: Uuid::new_v4(),
75 name,
76 command,
77 args,
78 directory,
79 autostart,
80 oneshot,
81 }
82 }
83 /// Returns the `uuid::Uuid` associated with the service config
84 pub fn get_uuid(&self) -> &Uuid {
85 &self.uuid
86 }
87 /// Returns the name of the described service
88 pub fn get_name(&self) -> &str {
89 &self.name
90 }
91 /// Returns the command of the described service
92 pub fn get_command(&self) -> &str {
93 &self.command
94 }
95 /// Returns the args of the described service
96 pub fn get_args(&self) -> &Option<String> {
97 &self.args
98 }
99 /// Returns the work directory of the described service
100 pub fn get_work_dir(&self) -> &Option<PathBuf> {
101 &self.directory
102 }
103 /// Returns the autostart status of the described service
104 pub fn get_autostart(&self) -> bool {
105 self.autostart
106 }
107 /// Returns the oneshot value of the described service
108 pub fn get_oneshot(&self) -> &Option<bool> {
109 &self.oneshot
110 }
111 /// Sets the name of the described service
112 pub fn name(&mut self, name: &str) -> &mut Self {
113 self.name = String::from(name);
114 self
115 }
116 /// Sets the command of the described service
117 pub fn command(&mut self, command: &str) -> &mut Self {
118 self.command = String::from(command);
119 self
120 }
121 /// Sets the args of the described service
122 pub fn args(&mut self, args: &Option<String>) -> &mut Self {
123 self.args = args.clone();
124 self
125 }
126 /// Sets the work directory of the described service
127 pub fn work_dir(&mut self, work_dir: &Option<PathBuf>) -> &mut Self {
128 self.directory = work_dir.clone();
129 self
130 }
131 /// Sets the autostart value of the described service
132 pub fn autostart(&mut self, autostart: bool) -> &mut Self {
133 self.autostart = autostart;
134 self
135 }
136 /// Sets the oneshot flag for the described service
137 pub fn oneshot(&mut self, oneshot: bool) -> &mut Self {
138 if oneshot {
139 self.oneshot = Some(true);
140 } else {
141 self.oneshot = None;
142 }
143 self
144 }
145 /// Builds a Service from this object
146 #[inline]
147 pub fn build(&self) -> Result<Service, Box<dyn std::error::Error>> {
148 Service::from_conf(&self)
149 }
150 }
151
152 #[derive(Debug)]
153 pub struct Service {
154 conf: ServiceConf,
155 proc: Option<Child>,
156 pub outpath: Option<PathBuf>,
157 pub errpath: Option<PathBuf>,
158 }
159 impl Default for Service {
160 fn default() -> Self {
161 Self::new()
162 }
163 }
164 impl<'a> Service {
165 /// Returns a new empty `Service`
166 pub fn new() -> Self {
167 Self {
168 conf: ServiceConf::default(),
169 proc: None,
170 outpath: None,
171 errpath: None,
172 }
173 }
174 /// Returns a `Service` made from a `ServiceConf`.
175 pub fn from_conf(conf: &ServiceConf) -> Result<Self, Box<dyn std::error::Error>> {
176 let mut service = Self {
177 conf: conf.to_owned(),
178 proc: None,
179 outpath: None,
180 errpath: None,
181 };
182 if conf.get_autostart() {
183 service.start()?;
184 }
185 Ok(service)
186 }
187 /// Gets the ServiceConf associated with the service
188 #[inline]
189 pub fn config(&self) -> &ServiceConf {
190 &self.conf
191 }
192 /// Returns the name of the service
193 #[inline]
194 pub fn name(&self) -> &str {
195 &self.config().get_name()
196 }
197 /// Returns the uuid of the service, shorthand for Service::config().get_uuid()
198 #[inline]
199 pub fn uuid(&self) -> &Uuid {
200 &self.config().get_uuid()
201 }
202 #[inline]
203 pub fn oneshot(&self) -> &Option<bool> {
204 &self.config().get_oneshot()
205 }
206 #[inline]
207 fn create_dirs(&self) -> Result<(), Box<dyn std::error::Error>> {
208 match std::fs::create_dir("./logs") {
209 Ok(_) => (),
210 Err(ref e) if e.kind() == std::io::ErrorKind::AlreadyExists => (),
211 Err(e) => return Err(Box::new(e)),
212 }
213 match std::fs::create_dir(format!("./logs/{}", &self.config().get_uuid())) {
214 Ok(_) => (),
215 Err(ref e) if e.kind() == std::io::ErrorKind::AlreadyExists => (),
216 Err(e) => return Err(Box::new(e)),
217 }
218 Ok(())
219 }
220 /// Uses `tokio::process::Command` to start the service.
221 pub fn start(&mut self) -> Result<(), Box<dyn std::error::Error>> {
222 if self.proc.is_some() {
223 return Err(Box::new(std::io::Error::new(
224 std::io::ErrorKind::AlreadyExists,
225 "Process Already Exists",
226 )));
227 }
228 self.create_dirs()?;
229 let outpath = PathBuf::from(format!(
230 "./logs/{}/{}.log",
231 &self.config().get_uuid(),
232 &self.name()
233 ));
234 let errpath = PathBuf::from(format!(
235 "./logs/{}/{}.err",
236 &self.config().get_uuid(),
237 &self.name()
238 ));
239 let outfile = File::options().append(true).create(true).open(&outpath)?;
240 let errfile = File::options().append(true).create(true).open(&errpath)?;
241 let cmd = &self.conf.command;
242 let mut proc = Command::new(cmd);
243 proc.stdin(Stdio::piped());
244 proc.stdout(outfile);
245 proc.stderr(errfile);
246 if let Some(a) = &self.conf.args {
247 proc.args(a.split_whitespace());
248 };
249 if let Some(c) = &self.conf.directory {
250 proc.current_dir(c);
251 };
252 let child = proc.spawn()?;
253 self.proc = Some(child);
254 self.outpath = Some(outpath);
255 self.errpath = Some(errpath);
256 Ok(())
257 }
258 /// Returns true when process is started and false when process is stopped.
259 pub fn started(&self) -> bool {
260 self.proc.is_some()
261 }
262 /// Returns the process id
263 pub fn id(&self) -> Result<u32, Box<dyn std::error::Error>> {
264 if let Some(proc) = self.proc.as_ref() {
265 Ok(proc.id())
266 } else {
267 Err(Box::new(std::io::Error::new(
268 std::io::ErrorKind::NotFound,
269 "process not started",
270 )))
271 }
272 }
273 /// Returns the state of the service
274 pub fn state(&mut self) -> ServiceState {
275 if let Some(proc) = self.proc.as_mut() {
276 match proc.try_wait() {
277 Err(_) | Ok(Some(_)) => {
278 if let Some(b) = self.oneshot() {
279 if *b {
280 return ServiceState::Stopped;
281 }
282 }
283 ServiceState::Failed
284 },
285 Ok(None) => ServiceState::Running,
286 }
287 } else {
288 ServiceState::Stopped
289 }
290 }
291 /// Invokes kill on the service process
292 pub fn stop(&mut self) -> Result<(), Box<dyn std::error::Error>> {
293 if let Some(proc) = self.proc.as_mut() {
294 proc.kill()?;
295 self.proc = None;
296 Ok(())
297 } else {
298 Err(Box::new(std::io::Error::new(
299 std::io::ErrorKind::NotFound,
300 "No Process Associated with Service",
301 )))
302 }
303 }
304 /// Restarts service process
305 #[inline]
306 pub fn restart(&mut self) -> Result<(), Box<dyn std::error::Error>> {
307 self.stop()?;
308 self.start()?;
309 Ok(())
310 }
311 /// Writes to the service process' stdin, if it exists.
312 pub fn write_stdin(&mut self, buf: &str) -> Result<(), Box<dyn std::error::Error>> {
313 if let Some(proc) = self.proc.as_mut() {
314 let stdin = if let Some(stdin) = proc.stdin.as_mut() {
315 stdin
316 } else {
317 return Err(Box::new(std::io::Error::new(
318 std::io::ErrorKind::NotFound,
319 "No stdin handle associated with process",
320 )));
321 };
322 stdin.write(&buf.as_bytes())?;
323 stdin.flush()?;
324 Ok(())
325 } else {
326 Err(Box::new(std::io::Error::new(
327 std::io::ErrorKind::NotFound,
328 "No Process Associated with Service",
329 )))
330 }
331 }
332 /// Writes a line to the service process' stdin, if it exists.
333 #[inline]
334 pub fn writeln_stdin(&mut self, buf: &str) -> Result<(), Box<dyn std::error::Error>> {
335 self.write_stdin(&format!("{}\n", buf))
336 }
337 }