ViewVC Help
View File | Revision Log | Show Annotations | View Changeset | Root Listing
root/salaryman/trunk/src/service.rs
Revision: 16
Committed: Tue Jul 29 09:26:15 2025 UTC (2 months, 1 week ago) by yuzu
File size: 9082 byte(s)
Log Message:
parallel process monitoring get

File Contents

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