1use std::{
2 borrow::Cow,
3 collections::HashMap,
4 env,
5 pin::Pin,
6 sync::{Arc, Mutex},
7 task::{Context, Poll},
8 time::Duration,
9};
10
11use anyhow::Result;
12use config::{Map, Value, ValueKind};
13use csscolorparser::Color;
14use derive_builder::Builder;
15use futures::{task::AtomicWaker, Stream};
16use lazy_static::lazy_static;
17use lazybar_types::EventResponse;
18use regex::{Captures, Regex};
19use tokio::{
20 io::AsyncWriteExt,
21 net::UnixStream,
22 time::{interval, Instant, Interval},
23};
24
25use crate::{ipc::ChannelEndpoint, parser};
26
27lazy_static! {
28 static ref REGEX: Regex = Regex::new(r"%\{(?<const>[^}]+)}").unwrap();
29}
30
31#[derive(Debug)]
34pub struct UnixStreamWrapper {
35 inner: UnixStream,
36 endpoint: ChannelEndpoint<String, EventResponse>,
37}
38
39impl UnixStreamWrapper {
40 pub const fn new(
42 inner: UnixStream,
43 endpoint: ChannelEndpoint<String, EventResponse>,
44 ) -> Self {
45 Self { inner, endpoint }
46 }
47
48 pub async fn run(mut self) -> Result<()> {
50 let mut data = [0; 1024];
51 self.inner.readable().await?;
52 let len = self.inner.try_read(&mut data)?;
53 let message = String::from_utf8_lossy(&data[0..len]);
54 if message.len() == 0 {
55 return Ok(());
56 }
57 self.endpoint.send.send(message.to_string())?;
58 let response = self
59 .endpoint
60 .recv
61 .recv()
62 .await
63 .unwrap_or(EventResponse::Ok(None));
64
65 self.inner.writable().await?;
66 self.inner
67 .try_write(serde_json::to_string(&response)?.as_bytes())?;
68
69 self.inner.shutdown().await?;
70
71 Ok(())
72 }
73}
74
75#[derive(Builder, Clone, Debug)]
87#[builder_struct_attr(allow(missing_docs))]
88#[builder_impl_attr(allow(missing_docs))]
89pub struct ManagedIntervalStream {
90 #[builder(
91 default = "Arc::new(Mutex::new(interval(Duration::from_secs(10))))"
92 )]
93 interval: Arc<Mutex<Interval>>,
94 #[builder(default)]
95 paused: Arc<Mutex<bool>>,
96 #[builder(default)]
97 waker: Arc<AtomicWaker>,
98}
99
100impl ManagedIntervalStream {
101 pub const fn new(
103 interval: Arc<Mutex<Interval>>,
104 paused: Arc<Mutex<bool>>,
105 waker: Arc<AtomicWaker>,
106 ) -> Self {
107 Self {
108 interval,
109 paused,
110 waker,
111 }
112 }
113
114 #[must_use]
117 pub fn builder() -> ManagedIntervalStreamBuilder {
118 ManagedIntervalStreamBuilder::default()
119 }
120}
121
122impl Stream for ManagedIntervalStream {
123 type Item = Instant;
124
125 fn poll_next(
126 self: Pin<&mut Self>,
127 cx: &mut Context<'_>,
128 ) -> Poll<Option<Self::Item>> {
129 self.waker.register(cx.waker());
130 if *self.paused.lock().unwrap() {
131 Poll::Pending
132 } else {
133 let val = self.interval.lock().unwrap().poll_tick(cx).map(Some);
134 val
135 }
136 }
137}
138
139impl AsRef<Arc<Mutex<Interval>>> for ManagedIntervalStream {
140 fn as_ref(&self) -> &Arc<Mutex<Interval>> {
141 &self.interval
142 }
143}
144
145impl AsMut<Arc<Mutex<Interval>>> for ManagedIntervalStream {
146 fn as_mut(&mut self) -> &mut Arc<Mutex<Interval>> {
147 &mut self.interval
148 }
149}
150
151impl ManagedIntervalStreamBuilder {
152 pub fn duration(&mut self, duration: Duration) -> &mut Self {
154 self.interval(Arc::new(Mutex::new(interval(duration))))
155 }
156}
157
158pub fn get_table_from_config<S: std::hash::BuildHasher>(
161 id: &str,
162 table: &HashMap<String, Value, S>,
163) -> Option<Map<String, Value>> {
164 table.get(id).and_then(|val| {
165 val.clone().into_table().map_or_else(
166 |_| {
167 log::warn!("Ignoring non-table value {val:?}");
168 None
169 },
170 Some,
171 )
172 })
173}
174
175pub fn remove_string_from_config<S: std::hash::BuildHasher>(
178 id: &str,
179 table: &mut HashMap<String, Value, S>,
180) -> Option<String> {
181 table.remove(id).and_then(|val| {
182 val.clone().into_string().map_or_else(
183 |_| {
184 log::warn!("Ignoring non-string value {val:?}");
185 None
186 },
187 |s| {
188 Some(
189 replace_consts(s.as_str(), parser::CONSTS.get().unwrap())
190 .to_string(),
191 )
192 },
193 )
194 })
195}
196
197pub fn remove_array_from_config<S: std::hash::BuildHasher>(
200 id: &str,
201 table: &mut HashMap<String, Value, S>,
202) -> Option<Vec<Value>> {
203 table.remove(id).and_then(|val| {
204 val.clone().into_array().map_or_else(
205 |_| {
206 log::warn!("Ignoring non-array value {val:?}");
207 None
208 },
209 |v| {
210 Some(
211 v.into_iter()
212 .map(|val| {
213 let origin = val.origin().map(ToString::to_string);
214 val.clone().into_string().map_or(val, |val| {
215 Value::new(
216 origin.as_ref(),
217 ValueKind::String(
218 replace_consts(
219 val.as_str(),
220 parser::CONSTS.get().unwrap(),
221 )
222 .to_string(),
223 ),
224 )
225 })
226 })
227 .collect(),
228 )
229 },
230 )
231 })
232}
233
234pub fn remove_uint_from_config<S: std::hash::BuildHasher>(
237 id: &str,
238 table: &mut HashMap<String, Value, S>,
239) -> Option<u64> {
240 table.remove(id).and_then(|val| {
241 val.clone().into_uint().map_or_else(
242 |_| {
243 log::warn!("Ignoring non-uint value {val:?}");
244 None
245 },
246 Some,
247 )
248 })
249}
250
251pub fn remove_bool_from_config<S: std::hash::BuildHasher>(
254 id: &str,
255 table: &mut HashMap<String, Value, S>,
256) -> Option<bool> {
257 table.remove(id).and_then(|val| {
258 val.clone().into_bool().map_or_else(
259 |_| {
260 log::warn!("Ignoring non-boolean value {val:?}");
261 None
262 },
263 Some,
264 )
265 })
266}
267
268pub fn remove_float_from_config<S: std::hash::BuildHasher>(
271 id: &str,
272 table: &mut HashMap<String, Value, S>,
273) -> Option<f64> {
274 table.remove(id).and_then(|val| {
275 val.clone().into_float().map_or_else(
276 |_| {
277 log::warn!("Ignoring non-float value {val:?}");
278 None
279 },
280 Some,
281 )
282 })
283}
284
285pub fn remove_color_from_config<S: std::hash::BuildHasher>(
288 id: &str,
289 table: &mut HashMap<String, Value, S>,
290) -> Option<Color> {
291 table.remove(id).and_then(|val| {
292 val.clone().into_string().map_or_else(
293 |_| {
294 log::warn!("Ignoring non-string value {val:?}");
295 None
296 },
297 |val| {
298 replace_consts(val.as_str(), parser::CONSTS.get().unwrap())
299 .parse()
300 .map_or_else(
301 |_| {
302 log::warn!("Invalid color {val}");
303 None
304 },
305 Some,
306 )
307 },
308 )
309 })
310}
311
312pub fn replace_consts<'a, S: std::hash::BuildHasher>(
315 format: &'a str,
316 consts: &HashMap<String, Value, S>,
317) -> Cow<'a, str> {
318 REGEX.replace_all(format, |caps: &Captures| {
319 let con = &caps["const"];
320 if let Some(c) = con.strip_prefix("env:") {
321 if let Ok(c) = env::var(c) {
322 return c;
323 }
324 }
325 consts
326 .get(con)
327 .and_then(|c| c.clone().into_string().ok())
328 .map_or_else(
329 || {
330 log::warn!("Invalid constant: {con}");
331 String::new()
332 },
333 |con| con,
334 )
335 })
336}