tiny_web/sys/
app.rs

1use std::{
2    io::{Read, Write},
3    net::TcpStream,
4    process::Command,
5    sync::Arc,
6    time::Duration,
7};
8
9#[cfg(not(target_family = "windows"))]
10use std::os::unix::net::UnixStream;
11
12use crate::{help::Help, sys::log::Log};
13
14use super::{
15    action::ActMap,
16    go::Go,
17    init::{Addr, Init, Mode},
18};
19
20/// Application information
21///
22/// # Values
23///
24/// * `init: Init` - Server configuration.
25#[derive(Debug)]
26pub(crate) struct App {
27    /// Server configuration.
28    pub init: Init,
29}
30
31impl App {
32    /// Initializes application
33    pub fn new(name: &str, version: &str, desc: &str, allow_no_config: bool) -> Option<App> {
34        let init = Init::new(name, version, desc, allow_no_config)?;
35        Some(App { init })
36    }
37
38    /// Run application
39    pub fn run(&self, func: &impl Fn() -> ActMap) {
40        Log::info(17, Some(format!("{:?}", self.init.mode)));
41        match self.init.mode {
42            Mode::Start => self.start(),
43            Mode::Stop => App::stop(Arc::clone(&self.init.conf.rpc), self.init.conf.stop_signal),
44            Mode::Help => Help::show(&self.init.conf.name, &self.init.conf.version, &self.init.conf.desc),
45            Mode::Go => Go::run(&self.init, func),
46            Mode::Status => self.status(),
47        };
48    }
49
50    /// Get status
51    fn status(&self) {
52        let mut buf: [u8; 8] = [0; 8];
53        let mut status = Vec::with_capacity(1024);
54        #[allow(clippy::infallible_destructuring_match)]
55        match self.init.conf.rpc.as_ref() {
56            Addr::SocketAddr(socket) => {
57                let mut tcp = match TcpStream::connect_timeout(socket, Duration::from_secs(2)) {
58                    Ok(t) => t,
59                    Err(e) => {
60                        Log::stop(213, Some(e.to_string()));
61                        return;
62                    }
63                };
64                // Send status to server into rpc channal
65                if let Err(e) = tcp.write(&self.init.conf.status_signal.to_be_bytes()) {
66                    Log::stop(224, Some(e.to_string()));
67                    return;
68                };
69                if let Err(e) = tcp.set_read_timeout(Some(Duration::from_secs(30))) {
70                    Log::stop(216, Some(e.to_string()));
71                    return;
72                };
73
74                // Reads answer
75                if let Err(e) = tcp.read_exact(&mut buf) {
76                    Log::stop(217, Some(e.to_string()));
77                    return;
78                };
79                if let Err(e) = tcp.read_to_end(&mut status) {
80                    Log::stop(225, Some(e.to_string()));
81                    return;
82                };
83            }
84            #[cfg(not(target_family = "windows"))]
85            Addr::Uds(path) => {
86                let mut tcp = match UnixStream::connect(path) {
87                    Ok(t) => t,
88                    Err(e) => {
89                        Log::stop(213, Some(e.to_string()));
90                        return;
91                    }
92                };
93                if let Err(e) = tcp.set_write_timeout(Some(Duration::new(2, 0))) {
94                    Log::stop(223, Some(e.to_string()));
95                    return;
96                };
97                if let Err(e) = tcp.write(&self.init.conf.status_signal.to_be_bytes()) {
98                    Log::stop(224, Some(e.to_string()));
99                    return;
100                };
101                if let Err(e) = tcp.set_read_timeout(Some(Duration::from_secs(30))) {
102                    Log::stop(216, Some(e.to_string()));
103                    return;
104                };
105
106                // Reads answer
107                if let Err(e) = tcp.read_exact(&mut buf) {
108                    Log::stop(217, Some(e.to_string()));
109                    return;
110                };
111                if let Err(e) = tcp.read_to_end(&mut status) {
112                    Log::stop(225, Some(e.to_string()));
113                    return;
114                };
115            }
116        };
117        let pid = u64::from_be_bytes(buf);
118        let answ = match String::from_utf8(status) {
119            Ok(a) => a,
120            Err(e) => {
121                Log::stop(226, Some(e.to_string()));
122                return;
123            }
124        };
125        println!("Answer PID={}\n{}", pid, answ);
126    }
127
128    /// Send stop signal
129    pub(crate) fn stop(rpc: Arc<Addr>, stop: i64) {
130        let mut buf: [u8; 8] = [0; 8];
131        #[allow(clippy::infallible_destructuring_match)]
132        match rpc.as_ref() {
133            Addr::SocketAddr(socket) => {
134                let mut tcp = match TcpStream::connect_timeout(socket, Duration::from_secs(2)) {
135                    Ok(t) => t,
136                    Err(e) => {
137                        Log::stop(213, Some(e.to_string()));
138                        return;
139                    }
140                };
141                // Send stop to server into rpc channal
142                if let Err(e) = tcp.write(&stop.to_be_bytes()) {
143                    Log::stop(214, Some(e.to_string()));
144                    return;
145                };
146                if let Err(e) = tcp.set_read_timeout(Some(Duration::from_secs(30))) {
147                    Log::stop(216, Some(e.to_string()));
148                    return;
149                };
150
151                // Reads answer
152                if let Err(e) = tcp.read_exact(&mut buf) {
153                    Log::stop(217, Some(e.to_string()));
154                    return;
155                };
156            }
157            #[cfg(not(target_family = "windows"))]
158            Addr::Uds(path) => {
159                let mut tcp = match UnixStream::connect(path) {
160                    Ok(t) => t,
161                    Err(e) => {
162                        Log::stop(213, Some(e.to_string()));
163                        return;
164                    }
165                };
166                if let Err(e) = tcp.set_write_timeout(Some(Duration::new(2, 0))) {
167                    Log::stop(223, Some(e.to_string()));
168                    return;
169                };
170                if let Err(e) = tcp.write(&stop.to_be_bytes()) {
171                    Log::stop(214, Some(e.to_string()));
172                    return;
173                };
174                if let Err(e) = tcp.set_read_timeout(Some(Duration::from_secs(30))) {
175                    Log::stop(216, Some(e.to_string()));
176                    return;
177                };
178
179                // Reads answer
180                if let Err(e) = tcp.read_exact(&mut buf) {
181                    Log::stop(217, Some(e.to_string()));
182                    return;
183                };
184            }
185        };
186
187        let pid = u64::from_be_bytes(buf);
188        Log::info(218, Some(format!("Answer PID={}", pid)));
189    }
190
191    /// Starting a new instance of the application in server mode for Windows
192    #[cfg(target_family = "windows")]
193    fn start(&self) {
194        let path = App::to_win_path(&self.init.exe_path);
195        let exe = App::to_win_path(&self.init.exe_file);
196        const DETACHED_PROCESS: u32 = 0x00000008;
197        const CREATE_NEW_PROCESS_GROUP: u32 = 0x00000200;
198        const CREATE_NO_WINDOW: u32 = 0x08000000;
199
200        let args = ["go", "-r", &self.init.root_path];
201        use std::os::windows::process::CommandExt;
202
203        match Command::new(&exe)
204            .args(args)
205            .current_dir(&path)
206            .creation_flags(DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP | CREATE_NO_WINDOW)
207            .spawn()
208        {
209            Ok(c) => Log::info(211, Some(format!("{} {}. PID: {}", &exe, args.join(" "), c.id()))),
210            Err(e) => Log::stop(212, Some(format!("{} {}. Error: {}", &exe, args.join(" "), e))),
211        };
212    }
213
214    /// Starting a new instance of the application in server mode for not Windows
215    #[cfg(not(target_family = "windows"))]
216    fn start(&self) {
217        let path = &self.init.exe_path;
218        let exe = &self.init.exe_file;
219
220        let args = ["go", "-r", &self.init.root_path];
221        match Command::new(exe).args(&args[..]).current_dir(path.as_ref()).spawn() {
222            Ok(c) => Log::info(211, Some(format!("{} {}. PID: {}", &exe, args.join(" "), c.id()))),
223            Err(e) => Log::stop(212, Some(format!("{} {}. Error: {}", &exe, args.join(" "), e))),
224        };
225    }
226
227    /// Convecrt unix path to windows
228    #[cfg(target_family = "windows")]
229    fn to_win_path(text: &str) -> String {
230        text.replace('/', r"\")
231    }
232}