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

# 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 }
25 impl Default for ServiceConf {
26 fn default() -> Self {
27 Self::new()
28 }
29 }
30 impl ServiceConf {
31 /// Returns a new empty `ServiceConf`
32 pub fn new() -> Self {
33 Self {
34 uuid: Uuid::new_v4(),
35 name: String::new(),
36 command: String::new(),
37 args: None,
38 directory: None,
39 autostart: false,
40 }
41 }
42 /// Returns a new `ServiceConf` from parts.
43 pub fn from_parts(
44 uuid: Uuid,
45 name: String,
46 command: String,
47 args: Option<String>,
48 directory: Option<PathBuf>,
49 autostart: bool,
50 ) -> Self {
51 Self {
52 uuid,
53 name,
54 command,
55 args,
56 directory,
57 autostart,
58 }
59 }
60 /// Returns a new `ServiceConf` from parts with new uuid.
61 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 /// 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 }
132
133 #[derive(Debug)]
134 pub struct Service {
135 conf: ServiceConf,
136 proc: Option<Child>,
137 pub outpath: Option<PathBuf>,
138 pub errpath: Option<PathBuf>,
139 }
140 impl Default for Service {
141 fn default() -> Self {
142 Self::new()
143 }
144 }
145 impl<'a> Service {
146 /// Returns a new empty `Service`
147 pub fn new() -> Self {
148 Self {
149 conf: ServiceConf::default(),
150 proc: None,
151 outpath: None,
152 errpath: None,
153 }
154 }
155 /// 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 proc: None,
160 outpath: None,
161 errpath: None,
162 };
163 if conf.get_autostart() {
164 service.start()?;
165 }
166 Ok(service)
167 }
168 /// Gets the ServiceConf associated with the service
169 #[inline]
170 pub fn config(&self) -> &ServiceConf {
171 &self.conf
172 }
173 /// 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 if self.proc.is_some() {
195 return Err(Box::new(std::io::Error::new(
196 std::io::ErrorKind::AlreadyExists,
197 "Process Already Exists",
198 )));
199 }
200 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 let cmd = &self.conf.command;
214 let mut proc = Command::new(cmd);
215 proc.stdin(Stdio::piped());
216 proc.stdout(outfile);
217 proc.stderr(errfile);
218 if let Some(a) = &self.conf.args {
219 proc.args(a.split_whitespace());
220 };
221 if let Some(c) = &self.conf.directory {
222 proc.current_dir(c);
223 };
224 let child = proc.spawn()?;
225 self.proc = Some(child);
226 self.outpath = Some(outpath);
227 self.errpath = Some(errpath);
228 Ok(())
229 }
230 /// Returns true when process is started and false when process is stopped.
231 pub fn started(&self) -> bool {
232 self.proc.is_some()
233 }
234 /// 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 } else {
239 Err(Box::new(std::io::Error::new(
240 std::io::ErrorKind::NotFound,
241 "process not started",
242 )))
243 }
244 }
245 /// 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 } else {
253 Ok(ServiceState::Stopped)
254 }
255 }
256 /// 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 Ok(())
262 } else {
263 Err(Box::new(std::io::Error::new(
264 std::io::ErrorKind::NotFound,
265 "No Process Associated with Service",
266 )))
267 }
268 }
269 /// 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 stdin
281 } else {
282 return Err(Box::new(std::io::Error::new(
283 std::io::ErrorKind::NotFound,
284 "No stdin handle associated with process",
285 )));
286 };
287 stdin.write(&buf.as_bytes())?;
288 stdin.flush()?;
289 Ok(())
290 } else {
291 Err(Box::new(std::io::Error::new(
292 std::io::ErrorKind::NotFound,
293 "No Process Associated with Service",
294 )))
295 }
296 }
297 /// Writes a line to the service process' stdin, if it exists.
298 #[inline]
299 pub fn writeln_stdin(&mut self, buf: &str) -> Result<(), Box<dyn std::error::Error>> {
300 self.write_stdin(&format!("{}\n", buf))
301 }
302 }