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

# 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 17 oneshot: Option<bool>,
25 yuzu 7 }
26 yuzu 8 impl Default for ServiceConf {
27     fn default() -> Self {
28     Self::new()
29     }
30     }
31 yuzu 7 impl ServiceConf {
32 yuzu 16 /// Returns a new empty `ServiceConf`
33 yuzu 7 pub fn new() -> Self {
34     Self {
35 yuzu 13 uuid: Uuid::new_v4(),
36 yuzu 7 name: String::new(),
37     command: String::new(),
38     args: None,
39     directory: None,
40     autostart: false,
41 yuzu 17 oneshot: None,
42 yuzu 7 }
43     }
44 yuzu 16 /// Returns a new `ServiceConf` from parts.
45 yuzu 7 pub fn from_parts(
46 yuzu 13 uuid: Uuid,
47 yuzu 7 name: String,
48     command: String,
49     args: Option<String>,
50     directory: Option<PathBuf>,
51     autostart: bool,
52 yuzu 17 oneshot: Option<bool>,
53 yuzu 7 ) -> Self {
54     Self {
55 yuzu 13 uuid,
56 yuzu 7 name,
57     command,
58     args,
59     directory,
60     autostart,
61 yuzu 17 oneshot,
62 yuzu 7 }
63     }
64 yuzu 16 /// Returns a new `ServiceConf` from parts with new uuid.
65 yuzu 13 pub fn new_from_parts(
66     name: String,
67     command: String,
68     args: Option<String>,
69     directory: Option<PathBuf>,
70     autostart: bool,
71 yuzu 17 oneshot: Option<bool>,
72 yuzu 13 ) -> Self {
73     Self {
74     uuid: Uuid::new_v4(),
75     name,
76     command,
77     args,
78     directory,
79     autostart,
80 yuzu 17 oneshot,
81 yuzu 13 }
82     }
83 yuzu 16 /// 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 yuzu 17 /// Returns the oneshot value of the described service
108     pub fn get_oneshot(&self) -> &Option<bool> {
109     &self.oneshot
110     }
111 yuzu 16 /// 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 yuzu 17 /// 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 yuzu 16 /// 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 yuzu 7 }
151    
152     #[derive(Debug)]
153     pub struct Service {
154 yuzu 8 conf: ServiceConf,
155 yuzu 16 proc: Option<Child>,
156     pub outpath: Option<PathBuf>,
157     pub errpath: Option<PathBuf>,
158 yuzu 7 }
159 yuzu 8 impl Default for Service {
160     fn default() -> Self {
161     Self::new()
162     }
163     }
164 yuzu 16 impl<'a> Service {
165     /// Returns a new empty `Service`
166 yuzu 7 pub fn new() -> Self {
167     Self {
168 yuzu 8 conf: ServiceConf::default(),
169     proc: None,
170 yuzu 16 outpath: None,
171     errpath: None,
172 yuzu 7 }
173     }
174 yuzu 16 /// 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 yuzu 8 proc: None,
179 yuzu 16 outpath: None,
180     errpath: None,
181     };
182     if conf.get_autostart() {
183     service.start()?;
184 yuzu 7 }
185 yuzu 16 Ok(service)
186 yuzu 7 }
187 yuzu 16 /// Gets the ServiceConf associated with the service
188     #[inline]
189     pub fn config(&self) -> &ServiceConf {
190     &self.conf
191 yuzu 8 }
192 yuzu 16 /// Returns the name of the service
193     #[inline]
194     pub fn name(&self) -> &str {
195     &self.config().get_name()
196     }
197 yuzu 17 /// Returns the uuid of the service, shorthand for Service::config().get_uuid()
198 yuzu 16 #[inline]
199 yuzu 17 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 yuzu 16 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 yuzu 8 if self.proc.is_some() {
223 yuzu 7 return Err(Box::new(std::io::Error::new(
224     std::io::ErrorKind::AlreadyExists,
225 yuzu 8 "Process Already Exists",
226 yuzu 7 )));
227     }
228 yuzu 16 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 yuzu 8 let cmd = &self.conf.command;
242 yuzu 9 let mut proc = Command::new(cmd);
243     proc.stdin(Stdio::piped());
244 yuzu 16 proc.stdout(outfile);
245     proc.stderr(errfile);
246 yuzu 9 if let Some(a) = &self.conf.args {
247     proc.args(a.split_whitespace());
248 yuzu 7 };
249 yuzu 9 if let Some(c) = &self.conf.directory {
250     proc.current_dir(c);
251 yuzu 7 };
252 yuzu 9 let child = proc.spawn()?;
253 yuzu 16 self.proc = Some(child);
254     self.outpath = Some(outpath);
255     self.errpath = Some(errpath);
256 yuzu 7 Ok(())
257     }
258 yuzu 16 /// Returns true when process is started and false when process is stopped.
259     pub fn started(&self) -> bool {
260 yuzu 8 self.proc.is_some()
261     }
262 yuzu 16 /// 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 yuzu 8 } else {
267     Err(Box::new(std::io::Error::new(
268     std::io::ErrorKind::NotFound,
269 yuzu 16 "process not started",
270 yuzu 8 )))
271     }
272     }
273 yuzu 16 /// Returns the state of the service
274 yuzu 17 pub fn state(&mut self) -> ServiceState {
275 yuzu 16 if let Some(proc) = self.proc.as_mut() {
276     match proc.try_wait() {
277 yuzu 17 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 yuzu 16 }
287 yuzu 7 } else {
288 yuzu 17 ServiceState::Stopped
289 yuzu 8 }
290 yuzu 7 }
291 yuzu 16 /// 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 yuzu 8 Ok(())
297 yuzu 7 } else {
298     Err(Box::new(std::io::Error::new(
299     std::io::ErrorKind::NotFound,
300 yuzu 8 "No Process Associated with Service",
301 yuzu 7 )))
302     }
303     }
304 yuzu 16 /// 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 yuzu 8 stdin
316 yuzu 7 } else {
317 yuzu 8 return Err(Box::new(std::io::Error::new(
318     std::io::ErrorKind::NotFound,
319     "No stdin handle associated with process",
320     )));
321     };
322 yuzu 16 stdin.write(&buf.as_bytes())?;
323     stdin.flush()?;
324 yuzu 8 Ok(())
325 yuzu 7 } else {
326     Err(Box::new(std::io::Error::new(
327     std::io::ErrorKind::NotFound,
328 yuzu 8 "No Process Associated with Service",
329 yuzu 7 )))
330     }
331     }
332 yuzu 16 /// Writes a line to the service process' stdin, if it exists.
333 yuzu 13 #[inline]
334 yuzu 16 pub fn writeln_stdin(&mut self, buf: &str) -> Result<(), Box<dyn std::error::Error>> {
335     self.write_stdin(&format!("{}\n", buf))
336 yuzu 13 }
337 yuzu 7 }