From 216cdbd53b8ec0c53286e527db46c5cbbf498d67 Mon Sep 17 00:00:00 2001 From: "J. Gerhards" Date: Fri, 2 Jan 2026 14:27:07 +0100 Subject: [PATCH 1/5] refactor: remove once_cell Remove the once_cell dependency in favour of the implementation in std --- Cargo.toml | 1 - src/config.rs | 5 ++--- src/main.rs | 5 ++--- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index fe5a693..0224854 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,6 @@ toml = "0.8.19" signal-hook = "0.3.17" async-std = { version = "1.12.0", features = ["attributes"] } futures-util = { version = "0.3.30", default-features = false, features = ["io"] } -once_cell = "1.19.0" libloading = "0.8.6" log = "0.4.25" simplelog = "0.12.2" diff --git a/src/config.rs b/src/config.rs index ad396ce..7a1ff30 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,10 +1,9 @@ use async_std::{fs, io, sync::RwLock}; use log::{info, warn}; -use once_cell::sync::OnceCell; use serde::{Deserialize, Serialize}; use std::net::{IpAddr, Ipv4Addr}; -use std::{env, path::Path, path::PathBuf}; +use std::{env, path::Path, path::PathBuf, sync::OnceLock}; use crate::HOME_DIR; use crate::args::Args; @@ -42,7 +41,7 @@ const DEFAULT_ADDR: IpAddr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); const DEFAULT_PORT: u16 = 6600; const DEFAULT_RETRIES: isize = 3; -pub static CONFIG: OnceCell> = OnceCell::new(); +pub static CONFIG: OnceLock> = OnceLock::new(); /// Returns a refrence to the global CONFIG value. /// diff --git a/src/main.rs b/src/main.rs index 6e5904b..c690d8c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,7 @@ use async_std::task::block_on; use libc::{EXIT_FAILURE, EXIT_SUCCESS, SIGHUP, SIGQUIT}; use log::{debug, error, info, warn}; -use once_cell::sync::Lazy; -use std::sync::{Arc, atomic::AtomicBool}; +use std::sync::{Arc, LazyLock, atomic::AtomicBool}; use std::{env, io, process::exit}; use signal_hook::{consts::TERM_SIGNALS, flag, iterator::Signals, low_level::emulate_default_handler}; @@ -20,7 +19,7 @@ mod util; #[rustfmt::skip] const VERSION_STR: &str = concat!("Running ", env!("CARGO_BIN_NAME"), " v", env!("CARGO_PKG_VERSION"), " (", env!("GIT_HASH"), ") compiled using rustc v", env!("RUSTC_VERSION")); -static HOME_DIR: Lazy = Lazy::new(|| env::var("HOME").expect("$HOME must be set")); +static HOME_DIR: LazyLock = LazyLock::new(|| env::home_dir().expect("the user should always have a home dir")); #[cfg(target_os = "linux")] fn main() { From f89856ce7a03270d6910cdefa9c07e66671ee7f8 Mon Sep 17 00:00:00 2001 From: "J. Gerhards" Date: Sun, 4 Jan 2026 04:53:43 +0100 Subject: [PATCH 2/5] refactor usage of HOME_DIR to make use of PathBuf::join() --- src/config.rs | 4 ++-- src/main.rs | 2 +- src/util/mod.rs | 7 ++----- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/config.rs b/src/config.rs index 7a1ff30..1c3e5d6 100644 --- a/src/config.rs +++ b/src/config.rs @@ -168,10 +168,10 @@ impl Config { } fn default_music_dir() -> PathBuf { - [&HOME_DIR, "Music"].iter().collect() + HOME_DIR.join("Music") } fn default_cover_dir() -> PathBuf { - [&HOME_DIR, "Music", "covers"].iter().collect() + HOME_DIR.join("Music/covers") } fn default_addr() -> IpAddr { DEFAULT_ADDR diff --git a/src/main.rs b/src/main.rs index c690d8c..12f6f4a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,7 @@ use async_std::task::block_on; use libc::{EXIT_FAILURE, EXIT_SUCCESS, SIGHUP, SIGQUIT}; use log::{debug, error, info, warn}; use std::sync::{Arc, LazyLock, atomic::AtomicBool}; -use std::{env, io, process::exit}; +use std::{env, io, process::exit, path::PathBuf}; use signal_hook::{consts::TERM_SIGNALS, flag, iterator::Signals, low_level::emulate_default_handler}; diff --git a/src/util/mod.rs b/src/util/mod.rs index 86c3b2c..5b86255 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -12,11 +12,8 @@ pub mod notify; /// Defined as: $XDG_CONFIG_PATH/mpdris/mpdris.conf or $HOME/.config/mpdris/mpdris.conf /// Deprecated path: $XDG_CONFIG_PATH/mpd/mpDris.conf or $HOME/.config/mpd/mpDris.conf pub fn get_config_path() -> PathBuf { - let base = env::var("XDG_CONFIG_HOME").unwrap_or_else(|_| format!("{}/.config", *HOME_DIR)); - let paths: [PathBuf; 2] = [ - [base.as_str(), "mpdris", "mpdris.conf"].iter().collect(), - [base.as_str(), "mpd", "mpDris.conf"].iter().collect(), - ]; + let base = env::var_os("XDG_CONFIG_HOME").map_or_else(|| HOME_DIR.join(".config"), |p| PathBuf::from(p)); + let paths = [base.join("mpdris/mpdris.conf"), base.join("mpd/mpDris.conf")]; let idx = paths.iter().position(|p| p.is_file()).unwrap_or(0); if idx == 1 { warn!("Using deprecated configuration path `{}`", paths[idx].display()); From ebd615510f417910f143970267980cd6ce1f7230 Mon Sep 17 00:00:00 2001 From: "J. Gerhards" Date: Sun, 4 Jan 2026 05:00:42 +0100 Subject: [PATCH 3/5] refactor expand.rs to work with PathBuf --- src/util/expand.rs | 174 +++++++++++++++++++++------------------------ 1 file changed, 83 insertions(+), 91 deletions(-) diff --git a/src/util/expand.rs b/src/util/expand.rs index c24d5c8..ff4d8c9 100644 --- a/src/util/expand.rs +++ b/src/util/expand.rs @@ -1,88 +1,77 @@ -use std::env; +use std::{env, ffi::OsString, path::PathBuf}; use log::warn; use serde::{Deserialize, Deserializer}; use crate::HOME_DIR; -pub fn serde_expand_path<'de, D: Deserializer<'de>, T: From>(de: D) -> Result { +pub fn serde_expand_path<'de, D: Deserializer<'de>, T: From>(de: D) -> Result { Ok(expand_path(&String::deserialize(de)?).into()) } -pub fn expand_path(str: &str) -> String { +pub fn expand_path(str: &str) -> PathBuf { if str == "~" { return HOME_DIR.clone(); - } else if (!str.contains('$') && !str.contains('~')) || str.chars().count() <= 1 { - return str.to_string(); + } else if (!str.contains('$') && !str.contains('~')) || str.chars().count() < 2 { + return PathBuf::from(str); } - let mut ret = String::with_capacity(str.len()); - // str has at least 2 chars because we checked it above let (first, second) = { let mut i = str.chars(); (i.next().unwrap(), i.next().unwrap()) }; - if first == '~' && second == '/' { - ret.reserve(HOME_DIR.len()); - - // this isn't actually unsafe because the content of HOME_DIR is always valid UTF-8 - unsafe { - ret.as_mut_vec().append(&mut HOME_DIR.clone().into_bytes()); - } - } + let mut ret = if first == '~' && second == '/' { + let home = HOME_DIR.as_os_str(); + let mut s = OsString::with_capacity(home.len() + str.len() - 2); + s.push(home); + s + } else { + OsString::with_capacity(str.len()) + }; - let mut remaining = if !ret.is_empty() { &str[1..] } else { str }; - while let Some(dollar_idx) = remaining.find('$') { - ret.push_str(&remaining[..dollar_idx]); + let mut rest = if !ret.is_empty() { &str[1..] } else { str }; + while let Some(dollar_idx) = rest.find('$') { + ret.push(&rest[..dollar_idx]); - remaining = &remaining[dollar_idx + 1..]; + rest = &rest[dollar_idx + 1..]; // if varname empty ignore it - if remaining.len() <= 1 || !is_valid_varname_char(remaining.as_bytes()[0] as char) { - ret.push('$'); + if rest.len() <= 1 || !is_valid_varname_char(rest.as_bytes()[0] as char) { + ret.push("$"); continue; } // if the dollar sign is escaped ignore it - if is_char_escaped(&str.as_bytes()[..dollar_idx]) { - ret.push('$'); + if is_char_escaped(&str[..dollar_idx]) { + ret.push("$"); continue; } - // go from dollar-idx until non-varname char - let mut end_idx = remaining.len() - 1; - for (i, chr) in remaining.chars().enumerate() { - if !is_valid_varname_char(chr) { - end_idx = i - 1; - break; - } - } + let end_idx = rest + .chars() + .position(|b| !is_valid_varname_char(b)) + .unwrap_or_else(|| rest.len()); - let varname = &remaining[..=end_idx]; - match env::var(varname) { - Ok(var) => { - ret.reserve(var.len()); - // this isn't actually unsafe because the content of var is always valid UTF-8 - unsafe { - ret.as_mut_vec().append(&mut var.into_bytes()); - } - } - Err(_e) => { + let varname = &rest[..end_idx]; + + match env::var_os(varname) { + Some(var) => ret.push(var), + None => { warn!("encountered undefined environment variable: {varname}"); + ret.reserve(varname.len() + 1); - ret.push('$'); - ret.push_str(varname); + ret.push("$"); + ret.push(varname); } } - remaining = &remaining[end_idx + 1..]; + rest = &rest[end_idx..]; } - ret.push_str(remaining); - - ret + ret.push(rest); + PathBuf::from(ret) } fn is_valid_varname_char(chr: char) -> bool { @@ -92,21 +81,19 @@ fn is_valid_varname_char(chr: char) -> bool { /// Checks if a char is backslash escaped by looking at the chars before it.
/// E.g. "\$" -> true; "\\$" -> false; "\\\$" -> true /// -/// the first byte of bytes should be the index after the char to check -fn is_char_escaped(bytes: &[u8]) -> bool { - if bytes.is_empty() { +/// the last char of s should be the char before the possibly escaped char. +fn is_char_escaped(s: &str) -> bool { + if s.is_empty() { return false; } let mut escaped = false; - let mut n = bytes.len(); - while n > 0 { - if bytes[n - 1] != b'\\' { + for chr in s.chars().rev() { + if chr != '\\' { break; } escaped = !escaped; - n -= 1; } escaped @@ -114,20 +101,22 @@ fn is_char_escaped(bytes: &[u8]) -> bool { #[cfg(test)] mod tests { + use std::path::Path; + use super::*; #[test] fn test_escaped_char() { - assert!(is_char_escaped(r"pr\\efi\x\".as_bytes())); - assert!(is_char_escaped(r"\".as_bytes())); - assert!(is_char_escaped(r"\postfix\".as_bytes())); - assert!(is_char_escaped(r"\\postfix\".as_bytes())); - assert!(!is_char_escaped(r"\postfix".as_bytes())); - assert!(!is_char_escaped(r"\\".as_bytes())); - assert!(!is_char_escaped(r"\\\\".as_bytes())); - assert!(!is_char_escaped(r"\\middle\\".as_bytes())); - assert!(!is_char_escaped(r"\\middle\f\\\\".as_bytes())); - assert!(!is_char_escaped(&[])); + assert!(is_char_escaped(r"pr\\efi\x\")); + assert!(is_char_escaped(r"\")); + assert!(is_char_escaped(r"\postfix\")); + assert!(is_char_escaped(r"\\postfix\")); + assert!(!is_char_escaped(r"\postfix")); + assert!(!is_char_escaped(r"\\")); + assert!(!is_char_escaped(r"\\\\")); + assert!(!is_char_escaped(r"\\middle\\")); + assert!(!is_char_escaped(r"\\middle\f\\\\")); + assert!(!is_char_escaped("")); } #[test] @@ -136,40 +125,43 @@ mod tests { env::remove_var("UNSET_VAR"); } - assert_eq!(expand_path("some/path"), "some/path"); - assert_eq!(expand_path("$UNSET_VAR/some/path"), "$UNSET_VAR/some/path"); - assert_eq!(expand_path("/some/$UNSET_VAR/path"), "/some/$UNSET_VAR/path"); - assert_eq!(expand_path("/some/path/$UNSET_VAR"), "/some/path/$UNSET_VAR"); - assert_eq!(expand_path("/some/path$"), "/some/path$"); - assert_eq!(expand_path("$/some/path"), "$/some/path"); - assert_eq!(expand_path("$"), "$"); + assert_eq!(expand_path("some/path"), Path::new("some/path")); + assert_eq!(expand_path("$UNSET_VAR/some/path"), Path::new("$UNSET_VAR/some/path")); + assert_eq!(expand_path("/some/$UNSET_VAR/path"), Path::new("/some/$UNSET_VAR/path")); + assert_eq!(expand_path("/some/path/$UNSET_VAR"), Path::new("/some/path/$UNSET_VAR")); + assert_eq!(expand_path("/some/path$"), Path::new("/some/path$")); + assert_eq!(expand_path("$/some/path"), Path::new("$/some/path")); + assert_eq!(expand_path("$"), Path::new("$")); } #[test] fn test_expansion() { unsafe { - env::set_var("HOME", "/home/repeatable"); - env::set_var("SOME_VAR", "relative"); + env::set_var("HOME", Path::new("/home/repeatable")); + env::set_var("SOME_VAR", Path::new("relative")); env::remove_var("UNSET_VAR"); } - assert_eq!(expand_path("~"), "/home/repeatable"); - assert_eq!(expand_path("~/"), "/home/repeatable/"); - assert_eq!(expand_path("/some/file/path/~/"), "/some/file/path/~/"); - assert_eq!(expand_path("~/some/dir/names"), "/home/repeatable/some/dir/names"); - assert_eq!(expand_path("~/ continue"), "/home/repeatable/ continue"); - assert_eq!(expand_path("~$UNSET_VAR"), "~$UNSET_VAR"); - assert_eq!(expand_path("~abcdef"), "~abcdef"); - assert_eq!(expand_path("~~"), "~~"); - assert_eq!(expand_path("~_"), "~_"); - - assert_eq!(expand_path("$HOME"), "/home/repeatable"); - assert_eq!(expand_path("$HOME-"), "/home/repeatable-"); - assert_eq!(expand_path("$HOME_/path"), "$HOME_/path"); - assert_eq!(expand_path("$HOME/$SOME_VAR/dir"), "/home/repeatable/relative/dir"); - assert_eq!(expand_path("$SOME_VAR/"), "relative/"); - assert_eq!(expand_path("/some/path/$SOME_VAR-SOME_VAR"), "/some/path/relative-SOME_VAR"); - assert_eq!(expand_path(r"/some/path/\$HOME"), r"/some/path/\$HOME"); - assert_eq!(expand_path("/some/path/$HOME_HOME"), "/some/path/$HOME_HOME"); + assert_eq!(expand_path("~"), Path::new("/home/repeatable")); + assert_eq!(expand_path("~/"), Path::new("/home/repeatable/")); + assert_eq!(expand_path("/some/file/path/~/"), Path::new("/some/file/path/~/")); + assert_eq!(expand_path("~/some/dir/names"), Path::new("/home/repeatable/some/dir/names")); + assert_eq!(expand_path("~/ continue"), Path::new("/home/repeatable/ continue")); + assert_eq!(expand_path("~$UNSET_VAR"), Path::new("~$UNSET_VAR")); + assert_eq!(expand_path("~abcdef"), Path::new("~abcdef")); + assert_eq!(expand_path("~~"), Path::new("~~")); + assert_eq!(expand_path("~_"), Path::new("~_")); + + assert_eq!(expand_path("$HOME"), Path::new("/home/repeatable")); + assert_eq!(expand_path("$HOME-"), Path::new("/home/repeatable-")); + assert_eq!(expand_path("$HOME_/path"), Path::new("$HOME_/path")); + assert_eq!(expand_path("$HOME/$SOME_VAR/dir"), Path::new("/home/repeatable/relative/dir")); + assert_eq!(expand_path("$SOME_VAR/"), Path::new("relative/")); + assert_eq!( + expand_path("/some/path/$SOME_VAR-SOME_VAR"), + Path::new("/some/path/relative-SOME_VAR") + ); + assert_eq!(expand_path(r"/some/path/\$HOME"), Path::new(r"/some/path/\$HOME")); + assert_eq!(expand_path("/some/path/$HOME_HOME"), Path::new("/some/path/$HOME_HOME")); } } From 7e03472b94a340b8d952287037c57dcb9c9aec93 Mon Sep 17 00:00:00 2001 From: "J. Gerhards" Date: Wed, 18 Mar 2026 19:57:34 +0100 Subject: [PATCH 4/5] update Cargo.lock --- Cargo.lock | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index da1be24..418cc60 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -920,7 +920,6 @@ dependencies = [ "libc", "libloading", "log", - "once_cell", "serde", "signal-hook", "simplelog", From 640a9bdcd9958fa51d2e1122cd6d9ffacfd9485a Mon Sep 17 00:00:00 2001 From: "J. Gerhards" Date: Wed, 18 Mar 2026 20:03:51 +0100 Subject: [PATCH 5/5] fix clippy lints --- src/main.rs | 2 +- src/util/expand.rs | 5 +---- src/util/mod.rs | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/main.rs b/src/main.rs index 12f6f4a..a4baadf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,7 @@ use async_std::task::block_on; use libc::{EXIT_FAILURE, EXIT_SUCCESS, SIGHUP, SIGQUIT}; use log::{debug, error, info, warn}; use std::sync::{Arc, LazyLock, atomic::AtomicBool}; -use std::{env, io, process::exit, path::PathBuf}; +use std::{env, io, path::PathBuf, process::exit}; use signal_hook::{consts::TERM_SIGNALS, flag, iterator::Signals, low_level::emulate_default_handler}; diff --git a/src/util/expand.rs b/src/util/expand.rs index ff4d8c9..d260064 100644 --- a/src/util/expand.rs +++ b/src/util/expand.rs @@ -49,10 +49,7 @@ pub fn expand_path(str: &str) -> PathBuf { continue; } - let end_idx = rest - .chars() - .position(|b| !is_valid_varname_char(b)) - .unwrap_or_else(|| rest.len()); + let end_idx = rest.chars().position(|b| !is_valid_varname_char(b)).unwrap_or(rest.len()); let varname = &rest[..end_idx]; diff --git a/src/util/mod.rs b/src/util/mod.rs index 5b86255..040345f 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -12,7 +12,7 @@ pub mod notify; /// Defined as: $XDG_CONFIG_PATH/mpdris/mpdris.conf or $HOME/.config/mpdris/mpdris.conf /// Deprecated path: $XDG_CONFIG_PATH/mpd/mpDris.conf or $HOME/.config/mpd/mpDris.conf pub fn get_config_path() -> PathBuf { - let base = env::var_os("XDG_CONFIG_HOME").map_or_else(|| HOME_DIR.join(".config"), |p| PathBuf::from(p)); + let base = env::var_os("XDG_CONFIG_HOME").map_or_else(|| HOME_DIR.join(".config"), PathBuf::from); let paths = [base.join("mpdris/mpdris.conf"), base.join("mpd/mpDris.conf")]; let idx = paths.iter().position(|p| p.is_file()).unwrap_or(0); if idx == 1 {