diff --git a/src/blocks/amd_gpu.rs b/src/blocks/amd_gpu.rs index 78fc4a053f..6eb763343e 100644 --- a/src/blocks/amd_gpu.rs +++ b/src/blocks/amd_gpu.rs @@ -63,7 +63,7 @@ pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { }; let device = match &config.device { - Some(name) => Device::new(name)?, + Some(name) => Device::new(name).await?, None => Device::default_card() .await .error("failed to get default GPU")? @@ -121,10 +121,13 @@ struct GpuInfo { } impl Device { - fn new(name: &str) -> Result { + async fn new(name: &str) -> Result { let path = PathBuf::from(format!("/sys/class/drm/{name}/device")); - if !path.exists() { + if !tokio::fs::try_exists(&path) + .await + .error("Unable to stat file")? + { Err(Error::new(format!("Device {name} not found"))) } else { Ok(Self { path }) @@ -185,9 +188,9 @@ impl Device { mod tests { use super::*; - #[test] - fn test_non_existing_gpu_device() { - let device = Device::new("/nope"); + #[tokio::test] + async fn test_non_existing_gpu_device() { + let device = Device::new("/nope").await; assert!(device.is_err()); } } diff --git a/src/blocks/calendar.rs b/src/blocks/calendar.rs index 8cf81da061..0c9b0f7982 100644 --- a/src/blocks/calendar.rs +++ b/src/blocks/calendar.rs @@ -295,7 +295,7 @@ pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { let warning_threshold = Duration::try_seconds(config.warning_threshold.into()) .error("Invalid warning threshold configuration")?; - let mut source = Source::new(source_config.clone()).await?; + let mut source = Source::new(source_config.to_owned()).await?; let mut timer = config.fetch_interval.timer(); @@ -424,7 +424,8 @@ impl Source { credentials_path, }) => { let credentials = if let Some(path) = credentials_path { - util::deserialize_toml_file(path.expand()?.to_string()) + util::async_deserialize_toml_file(path.expand()?.to_string()) + .await .error("Failed to read basic credentials file")? } else { credentials.clone() @@ -440,7 +441,8 @@ impl Source { } AuthConfig::OAuth2(oauth2) => { let credentials = if let Some(path) = &oauth2.credentials_path { - util::deserialize_toml_file(path.expand()?.to_string()) + util::async_deserialize_toml_file(path.expand()?.to_string()) + .await .error("Failed to read oauth2 credentials file")? } else { oauth2.credentials.clone() diff --git a/src/blocks/packages/apt.rs b/src/blocks/packages/apt.rs index 8d15692fc9..ac7668736e 100644 --- a/src/blocks/packages/apt.rs +++ b/src/blocks/packages/apt.rs @@ -50,7 +50,10 @@ impl Apt { async fn setup(&mut self) -> Result<()> { let mut cache_dir = env::temp_dir(); cache_dir.push("i3rs-apt"); - if !cache_dir.exists() { + if !tokio::fs::try_exists(&cache_dir) + .await + .error("Unable to stat file")? + { create_dir_all(&cache_dir) .await .error("Failed to create temp dir")?; diff --git a/src/blocks/packages/pacman.rs b/src/blocks/packages/pacman.rs index fc4da0935f..4a3237dc8d 100644 --- a/src/blocks/packages/pacman.rs +++ b/src/blocks/packages/pacman.rs @@ -72,7 +72,10 @@ impl Backend for Pacman { // Create symlink to local cache in `checkup-db` if required let local_cache = PACMAN_UPDATES_DB.join("local"); - if !local_cache.exists() { + if !tokio::fs::try_exists(&local_cache) + .await + .error("Unable to stat file")? + { symlink(PACMAN_DB.join("local"), local_cache) .await .error("Failed to created required symlink")?; diff --git a/src/icons.rs b/src/icons.rs index 76a7aa3bbb..3da89fcc19 100644 --- a/src/icons.rs +++ b/src/icons.rs @@ -121,7 +121,7 @@ impl Icons { if file == "none" { Ok(Icons::default()) } else { - let file = util::find_file(file, Some("icons"), Some("toml")) + let file = util::find_file(file, Some("icons"), Some("toml"))? .or_error(|| format!("Icon set '{file}' not found"))?; Ok(Icons(util::deserialize_toml_file(file)?)) } diff --git a/src/main.rs b/src/main.rs index 528564e020..5c582c98cc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -31,7 +31,7 @@ fn main() { .build() .unwrap() .block_on(async move { - let config_path = util::find_file(&args.config, None, Some("toml")) + let config_path = util::find_file(&args.config, None, Some("toml"))? .or_error(|| format!("Configuration file '{}' not found", args.config))?; let mut config: Config = util::deserialize_toml_file(&config_path)?; let blocks = std::mem::take(&mut config.blocks); diff --git a/src/netlink.rs b/src/netlink.rs index 80313b634d..df655f0495 100644 --- a/src/netlink.rs +++ b/src/netlink.rs @@ -77,7 +77,9 @@ impl NetDevice { let path = Path::new("/sys/class/net").join(&iface.name); let tun = iface.name.starts_with("tun") || iface.name.starts_with("tap") - || path.join("tun_flags").exists(); + || tokio::fs::try_exists(path.join("tun_flags")) + .await + .error("Unable to stat file")?; let (wg, ppp) = util::read_file(path.join("uevent")) .await .map_or((false, false), |c| { diff --git a/src/themes.rs b/src/themes.rs index c02b555e9d..11d75d804b 100644 --- a/src/themes.rs +++ b/src/themes.rs @@ -140,7 +140,7 @@ impl TryFrom for Theme { fn try_from(user_config: ThemeUserConfig) -> Result { let name = user_config.theme.as_deref().unwrap_or("plain"); - let file = util::find_file(name, Some("themes"), Some("toml")) + let file = util::find_file(name, Some("themes"), Some("toml"))? .or_error(|| format!("Theme '{name}' not found"))?; let theme: ThemeInner = util::deserialize_toml_file(file)?; let mut theme = Theme(theme); diff --git a/src/util.rs b/src/util.rs index 6b1a669900..10e29263b3 100644 --- a/src/util.rs +++ b/src/util.rs @@ -14,11 +14,15 @@ use crate::errors::*; /// - Then try `/usr/share/` /// /// Automatically append an extension if not presented. -pub fn find_file(file: &str, subdir: Option<&str>, extension: Option<&str>) -> Option { +pub fn find_file( + file: &str, + subdir: Option<&str>, + extension: Option<&str>, +) -> Result> { let file = Path::new(file); - if file.is_absolute() && file.exists() { - return Some(file.to_path_buf()); + if file.is_absolute() && file.try_exists().error("Unable to stat file")? { + return Ok(Some(file.to_path_buf())); } // Try XDG_CONFIG_HOME (e.g. `~/.config`) @@ -28,8 +32,8 @@ pub fn find_file(file: &str, subdir: Option<&str>, extension: Option<&str>) -> O xdg_config.push(subdir); } xdg_config.push(file); - if let Some(file) = exists_with_opt_extension(&xdg_config, extension) { - return Some(file); + if let Some(file) = exists_with_opt_extension(&xdg_config, extension)? { + return Ok(Some(file)); } } @@ -40,8 +44,8 @@ pub fn find_file(file: &str, subdir: Option<&str>, extension: Option<&str>) -> O xdg_data.push(subdir); } xdg_data.push(file); - if let Some(file) = exists_with_opt_extension(&xdg_data, extension) { - return Some(file); + if let Some(file) = exists_with_opt_extension(&xdg_data, extension)? { + return Ok(Some(file)); } } @@ -51,26 +55,26 @@ pub fn find_file(file: &str, subdir: Option<&str>, extension: Option<&str>) -> O usr_share_path.push(subdir); } usr_share_path.push(file); - if let Some(file) = exists_with_opt_extension(&usr_share_path, extension) { - return Some(file); + if let Some(file) = exists_with_opt_extension(&usr_share_path, extension)? { + return Ok(Some(file)); } - None + Ok(None) } -fn exists_with_opt_extension(file: &Path, extension: Option<&str>) -> Option { - if file.exists() { - return Some(file.into()); +fn exists_with_opt_extension(file: &Path, extension: Option<&str>) -> Result> { + if file.try_exists().error("Unable to stat file")? { + return Ok(Some(file.into())); } // If file has no extension, test with given extension if let (None, Some(extension)) = (file.extension(), extension) { let file = file.with_extension(extension); // Check again with extension added - if file.exists() { - return Some(file); + if file.try_exists().error("Unable to stat file")? { + return Ok(Some(file)); } } - None + Ok(None) } pub async fn new_dbus_connection() -> Result { @@ -95,6 +99,27 @@ where let contents = std::fs::read_to_string(path) .or_error(|| format!("Failed to read file: {}", path.display()))?; + deserialize_toml_file_string(contents, path) +} + +pub async fn async_deserialize_toml_file(path: P) -> Result +where + T: DeserializeOwned, + P: AsRef, +{ + let path = path.as_ref(); + + let contents = read_file(path) + .await + .or_error(|| format!("Failed to read file: {}", path.display()))?; + + deserialize_toml_file_string(contents, path) +} + +fn deserialize_toml_file_string(contents: String, path: &Path) -> Result +where + T: DeserializeOwned, +{ toml::from_str(&contents).map_err(|err| { let location_msg = err .span()