rs_blocks/
utils.rs

1// Copyright ⓒ 2019-2021 Lewis Belcher
2// Licensed under the MIT license (see LICENSE or <http://opensource.org/licenses/MIT>).
3// All files in the project carrying such notice may not be copied, modified, or
4// distributed except according to those terms
5
6use std::fs::{self, File};
7use std::io::{self, Read, Seek, SeekFrom};
8use std::num;
9use std::process::Command;
10use std::thread;
11use std::time::Duration;
12
13/// Seek to the beginning of a file and read all its contents into a string.
14fn read_to_string(f: &mut File, mut buf: &mut String) -> io::Result<()> {
15	f.seek(SeekFrom::Start(0))?;
16	f.read_to_string(&mut buf)?;
17	Ok(())
18}
19
20/// Parse a string as a float32.
21pub fn str_to_f32(s: &str) -> Result<f32, num::ParseFloatError> {
22	s.trim().parse()
23}
24
25/// Parse a string as a float32.
26pub fn file_to_f32(path: &str) -> Result<f32, num::ParseFloatError> {
27	fs::read_to_string(path).unwrap().trim().parse()
28}
29
30/// A monitoring abstraction which will periodically call `reader` when iterated.
31pub struct Monitor<T>
32where
33	T: FnMut() -> String,
34{
35	reader: T,
36	period: Duration,
37	first: bool,
38}
39
40impl<T> Monitor<T>
41where
42	T: FnMut() -> String,
43{
44	fn new(reader: T, period: f32) -> Self {
45		Monitor {
46			reader,
47			period: Duration::from_secs_f32(period),
48			first: true,
49		}
50	}
51
52	pub fn read(&mut self) -> String {
53		(self.reader)()
54	}
55}
56
57impl<T> Iterator for Monitor<T>
58where
59	T: FnMut() -> String,
60{
61	type Item = String;
62
63	fn next(&mut self) -> Option<Self::Item> {
64		if self.first {
65			self.first = false;
66		} else {
67			thread::sleep(self.period);
68		}
69		Some(self.read())
70	}
71}
72
73/// Monitor a file at a given path. When iterated it's contents are periodically
74/// read. NB the file should already have been verified to exist at this point,
75/// so we should safe to expect that it still does.
76pub fn monitor_file(path: String, period: f32) -> Monitor<impl FnMut() -> String> {
77	let mut file = File::open(&path).unwrap();
78	let mut buf = String::new();
79	Monitor::new(
80		move || {
81			buf.truncate(0);
82			if let Ok(_) = read_to_string(&mut file, &mut buf) {
83				buf.clone()
84			} else {
85				format!("Failed to read: {}", &path)
86			}
87		},
88		period,
89	)
90}
91
92/// Monitor a given command. When iterated it is periodically executed and its
93/// stdout is returned.
94pub fn monitor_command(
95	cmd: &'static str,
96	args: &'static [&'static str],
97	period: f32,
98) -> Monitor<impl FnMut() -> String> {
99	Monitor::new(
100		move || {
101			if let Ok(output) = Command::new(cmd).args(args).output() {
102				String::from_utf8(output.stdout).unwrap()
103			} else {
104				format!("Command failed: '{:?}'", &cmd)
105			}
106		},
107		period,
108	)
109}
110
111/// Wait for a signal to occur with a given timeout. Sends a message through
112/// the returned receiver when the signal/timeout occurs.
113pub fn wait_for_signal(signal: i32, timeout: f32) -> crossbeam_channel::Receiver<()> {
114	let (s, r) = crossbeam_channel::unbounded();
115	let s2 = s.clone();
116	unsafe {
117		signal_hook::register(signal, move || s2.send(()).unwrap()).unwrap();
118	}
119	thread::spawn(move || loop {
120		thread::sleep(Duration::from_secs_f32(timeout));
121		s.send(()).unwrap();
122	});
123	r
124}