Skip to content

Commit 18b7929

Browse files
authored
Initial port to async/await with smol (#146)
1 parent ea4c8c6 commit 18b7929

File tree

24 files changed

+1226
-747
lines changed

24 files changed

+1226
-747
lines changed

Cargo.lock

Lines changed: 378 additions & 64 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ xdg = "~2.5"
2525
dbus = "~0.9"
2626
anyhow = "~1.0"
2727
drm-fourcc = "~2.2"
28-
29-
[dev-dependencies]
30-
mockall = "0.13"
28+
smol = "~2.0"
29+
smol-macros = "~0.1"
30+
futures-util = "~0.3"
31+
macro_rules_attribute = "~0.2"

src/als/controller.rs

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,39 @@
1+
use smol::channel::Sender;
2+
use smol::Timer;
3+
14
use super::Als;
2-
use std::sync::mpsc::Sender;
3-
use std::thread;
45
use std::time::Duration;
56

67
const WAITING_SLEEP_MS: u64 = 100;
78

89
pub struct Controller {
9-
als: Box<dyn Als>,
10+
als: Als,
1011
value_txs: Vec<Sender<String>>,
1112
}
1213

1314
impl Controller {
14-
pub fn new(als: Box<dyn Als>, value_txs: Vec<Sender<String>>) -> Self {
15+
pub fn new(als: Als, value_txs: Vec<Sender<String>>) -> Self {
1516
Self { als, value_txs }
1617
}
1718

18-
pub fn run(&mut self) {
19+
pub async fn run(&mut self) {
1920
loop {
20-
self.step();
21+
self.step().await;
2122
}
2223
}
2324

24-
fn step(&mut self) {
25-
match self.als.get() {
25+
async fn step(&mut self) {
26+
match self.als.get().await {
2627
Ok(value) => {
27-
self.value_txs.iter().for_each(|chan| {
28-
chan.send(value.clone())
29-
.expect("Unable to send new ALS value, channel is dead")
30-
});
28+
futures_util::future::try_join_all(
29+
self.value_txs.iter().map(|chan| chan.send(value.clone())),
30+
)
31+
.await
32+
.expect("Unable to send new ALS value, channel is dead");
3133
}
3234
Err(err) => log::error!("Unable to get ALS value: {:?}", err),
3335
};
3436

35-
thread::sleep(Duration::from_millis(WAITING_SLEEP_MS));
37+
Timer::after(Duration::from_millis(WAITING_SLEEP_MS)).await;
3638
}
3739
}

src/als/iio.rs

Lines changed: 84 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
use crate::device_file::read;
2+
use crate::ErrorBox;
3+
use futures_util::{FutureExt, StreamExt, TryFutureExt};
4+
use smol::fs::File;
5+
use smol::lock::Mutex;
26
use std::collections::HashMap;
3-
use std::error::Error;
4-
use std::fs;
5-
use std::fs::File;
7+
use std::ops::DerefMut;
68
use std::path::{Path, PathBuf};
7-
use std::sync::Mutex;
89
use SensorType::*;
910

11+
#[allow(clippy::large_enum_variant)]
1012
enum SensorType {
1113
Illuminance {
1214
value: Mutex<File>,
@@ -26,111 +28,129 @@ pub struct Als {
2628
}
2729

2830
impl Als {
29-
pub fn new(base_path: &str, thresholds: HashMap<u64, String>) -> Result<Self, Box<dyn Error>> {
30-
Path::new(base_path)
31-
.read_dir()
32-
.ok()
33-
.and_then(|dir| {
34-
dir.filter_map(|e| e.ok())
35-
.find(|e| {
36-
["als", "acpi-als", "apds9960"].contains(
37-
&fs::read_to_string(e.path().join("name"))
38-
.unwrap_or_default()
39-
.trim(),
40-
)
41-
})
42-
.and_then(|e| {
43-
// TODO should probably start from the `parse_illuminance_input` in the next major version
44-
parse_illuminance_raw(e.path())
45-
.or_else(|_| parse_illuminance_input(e.path()))
46-
.or_else(|_| parse_intensity_raw(e.path()))
47-
.or_else(|_| parse_intensity_rgb(e.path()))
48-
.ok()
31+
pub async fn new(base_path: &str, thresholds: HashMap<u64, String>) -> Result<Self, ErrorBox> {
32+
smol::fs::read_dir(base_path)
33+
.await
34+
.map_err(|e| ErrorBox::from(format!("Can't enumerate iio devices: {e}")))?
35+
.filter_map(|r| async { r.ok() })
36+
.then(|entry| {
37+
smol::fs::read_to_string(entry.path().join("name")).map(|name| (name, entry))
38+
})
39+
.filter_map(|(name, entry)| async {
40+
["als", "acpi-als", "apds9960"]
41+
.contains(&name.unwrap_or_default().trim())
42+
.then_some(entry)
43+
})
44+
.filter_map(|entry| async move {
45+
// TODO should probably start from the `parse_illuminance_input` in the next major version
46+
parse_illuminance_raw(entry.path())
47+
.or_else(|_| parse_illuminance_input(entry.path()))
48+
.or_else(|_| parse_intensity_raw(entry.path()))
49+
.or_else(|_| parse_intensity_rgb(entry.path()))
50+
.await
51+
.map(Some)
52+
.unwrap_or_else(|_| {
53+
log::error!("Failed to read sensor '{}'", entry.path().display());
54+
None
4955
})
5056
})
57+
.boxed()
58+
.next()
59+
.await
5160
.map(|sensor| Self { sensor, thresholds })
52-
.ok_or_else(|| "No iio device found".into())
61+
.ok_or_else(|| ErrorBox::from("No iio device found"))
62+
}
63+
64+
pub async fn get(&self) -> Result<String, ErrorBox> {
65+
let raw = self.get_raw().await?;
66+
let profile = super::find_profile(raw, &self.thresholds);
67+
68+
log::trace!("ALS (iio): {} ({})", profile, raw);
69+
Ok(profile)
5370
}
5471

55-
fn get_raw(&self) -> Result<u64, Box<dyn Error>> {
72+
async fn get_raw(&self) -> Result<u64, ErrorBox> {
5673
Ok(match self.sensor {
5774
Illuminance {
5875
ref value,
5976
scale,
6077
offset,
61-
} => (read(&mut value.lock().unwrap())? + offset) * scale,
78+
} => (read(value.lock().await.deref_mut()).await? + offset) * scale,
6279

6380
Intensity {
6481
ref r,
6582
ref g,
6683
ref b,
6784
} => {
68-
-0.32466 * read(&mut r.lock().unwrap())?
69-
+ 1.57837 * read(&mut g.lock().unwrap())?
70-
+ -0.73191 * read(&mut b.lock().unwrap())?
85+
-0.32466 * read(r.lock().await.deref_mut()).await?
86+
+ 1.57837 * read(g.lock().await.deref_mut()).await?
87+
+ -0.73191 * read(b.lock().await.deref_mut()).await?
7188
}
7289
} as u64)
7390
}
7491
}
7592

76-
impl super::Als for Als {
77-
fn get(&self) -> Result<String, Box<dyn Error>> {
78-
let raw = self.get_raw()?;
79-
let profile = super::find_profile(raw, &self.thresholds);
80-
81-
log::trace!("ALS (iio): {} ({})", profile, raw);
82-
Ok(profile)
83-
}
84-
}
85-
86-
fn parse_illuminance_raw(path: PathBuf) -> Result<SensorType, Box<dyn Error>> {
93+
async fn parse_illuminance_raw(path: PathBuf) -> Result<SensorType, ErrorBox> {
8794
Ok(Illuminance {
8895
value: Mutex::new(
8996
open_file(&path, "in_illuminance_raw")
90-
.or_else(|_| open_file(&path, "in_illuminance0_raw"))?,
97+
.or_else(|_| open_file(&path, "in_illuminance0_raw"))
98+
.await?,
9199
),
92-
scale: open_file(&path, "in_illuminance_scale")
93-
.or_else(|_| open_file(&path, "in_illuminance0_scale"))
94-
.and_then(|mut f| read(&mut f))
95-
.unwrap_or(1_f64),
96-
offset: open_file(&path, "in_illuminance_offset")
97-
.or_else(|_| open_file(&path, "in_illuminance0_offset"))
98-
.and_then(|mut f| read(&mut f))
99-
.unwrap_or(0_f64),
100+
scale: {
101+
open_file(&path, "in_illuminance_scale")
102+
.or_else(|_| open_file(&path, "in_illuminance0_scale"))
103+
.and_then(move |mut f| async move { read(&mut f).await })
104+
.await
105+
.unwrap_or(1_f64)
106+
},
107+
offset: {
108+
open_file(&path, "in_illuminance_offset")
109+
.or_else(|_| open_file(&path, "in_illuminance0_offset"))
110+
.and_then(move |mut f| async move { read(&mut f).await })
111+
.await
112+
.unwrap_or(0_f64)
113+
},
100114
})
101115
}
102116

103-
fn parse_intensity_raw(path: PathBuf) -> Result<SensorType, Box<dyn Error>> {
117+
async fn parse_intensity_raw(path: PathBuf) -> Result<SensorType, ErrorBox> {
118+
async fn try_open_and_read(path: &Path, name: &str) -> Result<f64, ErrorBox> {
119+
let mut f = open_file(path, name).await?;
120+
read(&mut f).await
121+
}
122+
104123
Ok(Illuminance {
105-
value: Mutex::new(open_file(&path, "in_intensity_both_raw")?),
106-
scale: open_file(&path, "in_intensity_scale")
107-
.and_then(|mut f| read(&mut f))
124+
value: Mutex::new(open_file(&path, "in_intensity_both_raw").await?),
125+
scale: try_open_and_read(&path, "in_intensity_scale")
126+
.await
108127
.unwrap_or(1_f64),
109-
offset: open_file(&path, "in_intensity_offset")
110-
.and_then(|mut f| read(&mut f))
128+
offset: try_open_and_read(&path, "in_intensity_offset")
129+
.await
111130
.unwrap_or(0_f64),
112131
})
113132
}
114133

115-
fn parse_illuminance_input(path: PathBuf) -> Result<SensorType, Box<dyn Error>> {
134+
async fn parse_illuminance_input(path: PathBuf) -> Result<SensorType, ErrorBox> {
116135
Ok(Illuminance {
117136
value: Mutex::new(
118137
open_file(&path, "in_illuminance_input")
119-
.or_else(|_| open_file(&path, "in_illuminance0_input"))?,
138+
.or_else(|_| open_file(&path, "in_illuminance0_input"))
139+
.await?,
120140
),
121141
scale: 1_f64,
122142
offset: 0_f64,
123143
})
124144
}
125145

126-
fn parse_intensity_rgb(path: PathBuf) -> Result<SensorType, Box<dyn Error>> {
146+
async fn parse_intensity_rgb(path: PathBuf) -> Result<SensorType, ErrorBox> {
127147
Ok(Intensity {
128-
r: Mutex::new(open_file(&path, "in_intensity_red_raw")?),
129-
g: Mutex::new(open_file(&path, "in_intensity_green_raw")?),
130-
b: Mutex::new(open_file(&path, "in_intensity_blue_raw")?),
148+
r: Mutex::new(open_file(&path, "in_intensity_red_raw").await?),
149+
g: Mutex::new(open_file(&path, "in_intensity_green_raw").await?),
150+
b: Mutex::new(open_file(&path, "in_intensity_blue_raw").await?),
131151
})
132152
}
133153

134-
fn open_file(path: &Path, name: &str) -> Result<File, Box<dyn Error>> {
135-
File::open(path.join(name)).map_err(Box::<dyn Error>::from)
154+
async fn open_file(path: &Path, name: &str) -> Result<File, ErrorBox> {
155+
File::open(path.join(name)).await.map_err(ErrorBox::from)
136156
}

src/als/mod.rs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,31 @@
11
use itertools::Itertools;
22
use std::collections::HashMap;
3-
use std::error::Error;
3+
4+
use crate::ErrorBox;
45

56
pub mod controller;
67
pub mod iio;
78
pub mod none;
89
pub mod time;
910
pub mod webcam;
1011

11-
pub trait Als {
12-
fn get(&self) -> Result<String, Box<dyn Error>>;
12+
#[allow(clippy::large_enum_variant)]
13+
pub enum Als {
14+
Webcam(webcam::Als),
15+
Iio(iio::Als),
16+
Time(time::Als),
17+
None(none::Als),
18+
}
19+
20+
impl Als {
21+
pub async fn get(&self) -> Result<String, ErrorBox> {
22+
match self {
23+
Als::Webcam(als) => als.get().await,
24+
Als::Iio(als) => als.get().await,
25+
Als::Time(als) => als.get().await,
26+
Als::None(als) => als.get().await,
27+
}
28+
}
1329
}
1430

1531
fn find_profile(raw: u64, thresholds: &HashMap<u64, String>) -> String {

src/als/none.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
use std::error::Error;
1+
use crate::ErrorBox;
22

33
#[derive(Default)]
44
pub struct Als {}
55

6-
impl super::Als for Als {
7-
fn get(&self) -> Result<String, Box<dyn Error>> {
6+
impl Als {
7+
pub async fn get(&self) -> Result<String, ErrorBox> {
88
Ok("none".to_string())
99
}
1010
}

src/als/time.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use chrono::{Local, Timelike};
22
use std::collections::HashMap;
3-
use std::error::Error;
3+
4+
use crate::ErrorBox;
45

56
pub struct Als {
67
thresholds: HashMap<u64, String>,
@@ -10,10 +11,8 @@ impl Als {
1011
pub fn new(thresholds: HashMap<u64, String>) -> Self {
1112
Self { thresholds }
1213
}
13-
}
1414

15-
impl super::Als for Als {
16-
fn get(&self) -> Result<String, Box<dyn Error>> {
15+
pub async fn get(&self) -> Result<String, ErrorBox> {
1716
let raw = Local::now().hour() as u64;
1817
let profile = super::find_profile(raw, &self.thresholds);
1918

0 commit comments

Comments
 (0)