1#![deny(clippy::missing_inline_in_public_items)]
2
3use errors::DirectoryError;
4
5#[cfg(feature = "audio")]
6pub mod audio;
7pub mod config;
8pub mod errors;
9pub mod logger;
10#[cfg(feature = "rpc")]
11pub mod rpc;
12pub mod state;
13#[cfg(any(test, feature = "test_utils"))]
14pub mod test_utils;
15#[cfg(feature = "rpc")]
16pub mod udp;
17
18#[cfg(test)]
19extern crate rstest_reuse;
20
21#[macro_export]
44macro_rules! function_name {
45 () => {{
46 const fn f() {}
48 fn type_name_of<T>(_: T) -> &'static str {
49 std::any::type_name::<T>()
50 }
51 let name = type_name_of(f);
52 &name[..name.len() - 3]
54 }};
55}
56
57#[must_use]
58#[inline]
59pub fn format_duration(duration: &std::time::Duration) -> String {
60 let total_seconds = duration.as_secs();
61 let hours = total_seconds / 3600;
62 let minutes = (total_seconds % 3600) / 60;
63 let seconds = duration.as_secs_f32() % 60.;
64
65 format!("{hours:02}:{minutes:02}:{seconds:05.2}")
66}
67
68#[inline]
77pub fn get_data_dir() -> Result<std::path::PathBuf, DirectoryError> {
78 let directory = if let Ok(s) = std::env::var("MECOMP_DATA") {
79 std::path::PathBuf::from(s)
80 } else if let Some(proj_dirs) =
81 directories::ProjectDirs::from("com", "anthonymichaeltdm", "mecomp")
82 {
83 proj_dirs.data_local_dir().to_path_buf()
84 } else {
85 return Err(DirectoryError::Data);
86 };
87 Ok(directory)
88}
89
90#[inline]
99pub fn get_config_dir() -> Result<std::path::PathBuf, DirectoryError> {
100 let directory = if let Ok(s) = std::env::var("MECOMP_CONFIG") {
101 std::path::PathBuf::from(s)
102 } else if let Some(proj_dirs) =
103 directories::ProjectDirs::from("com", "anthonymichaeltdm", "mecomp")
104 {
105 proj_dirs.config_local_dir().to_path_buf()
106 } else {
107 return Err(DirectoryError::Config);
108 };
109 Ok(directory)
110}
111
112#[must_use]
115#[inline]
116pub fn is_server_running(port: u16) -> bool {
117 std::net::TcpStream::connect(format!("localhost:{port}")).is_ok()
118}
119
120#[derive(Debug, Clone)]
122pub struct OnceLockDefault<T> {
123 value: std::sync::OnceLock<T>,
124 default: T,
125}
126
127impl<T> OnceLockDefault<T> {
128 #[inline]
130 pub const fn new(default: T) -> Self {
131 Self {
132 value: std::sync::OnceLock::new(),
133 default,
134 }
135 }
136
137 #[inline]
146 pub fn set(&self, value: T) -> Result<(), T> {
147 self.value.set(value)
148 }
149
150 #[inline]
154 pub fn get(&self) -> &T {
155 self.value.get().unwrap_or(&self.default)
156 }
157
158 #[inline]
162 pub fn is_initialized(&self) -> bool {
163 self.value.get().is_some()
164 }
165}
166
167impl<T> std::ops::Deref for OnceLockDefault<T> {
168 type Target = T;
169
170 #[inline]
171 fn deref(&self) -> &Self::Target {
172 self.get()
173 }
174}
175
176#[cfg(test)]
177mod test {
178 use super::format_duration;
179 use pretty_assertions::assert_eq;
180 use rstest::rstest;
181 use std::time::Duration;
182
183 #[rstest]
184 #[case::zero(Duration::from_secs(0), "00:00:00.00")]
185 #[case::sub_second(Duration::from_millis(100), "00:00:00.10")]
186 #[case::sub_second(Duration::from_millis(101), "00:00:00.10")]
187 #[case::one_second(Duration::from_secs(1), "00:00:01.00")]
188 #[case::one_minute(Duration::from_secs(60), "00:01:00.00")]
189 #[case::one_hour(Duration::from_secs(3600), "01:00:00.00")]
190 #[case::one_hour_one_minute_one_second(Duration::from_secs(3661), "01:01:01.00")]
191 #[case(Duration::from_secs(3600 + 120 + 1), "01:02:01.00")]
192 fn test_format_duration(#[case] duration: Duration, #[case] expected: &str) {
193 let actual = format_duration(&duration);
194 assert_eq!(actual, expected);
195 }
196
197 #[test]
198 fn test_function_name() {
199 fn test_function() {
200 let result = super::function_name!();
201 assert!(result.ends_with("test_function"));
202 }
203
204 test_function();
205 }
206
207 #[test]
208 fn test_get_data_dir() {
209 let data_dir = super::get_data_dir().unwrap();
210 assert_eq!(
211 data_dir
212 .components()
213 .next_back()
214 .unwrap()
215 .as_os_str()
216 .to_string_lossy(),
217 "mecomp"
218 );
219 }
220
221 #[test]
222 fn test_get_config_dir() {
223 let config_dir = super::get_config_dir().unwrap();
224 assert_eq!(
225 config_dir
226 .components()
227 .next_back()
228 .unwrap()
229 .as_os_str()
230 .to_string_lossy(),
231 "mecomp"
232 );
233 }
234}