finger: implement basic fuzzy search mechanism
This commit is contained in:
Generated
+10
@@ -845,6 +845,7 @@ dependencies = [
|
||||
"toml",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"users",
|
||||
"uucore",
|
||||
"zlink",
|
||||
]
|
||||
@@ -1286,6 +1287,15 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
|
||||
|
||||
[[package]]
|
||||
name = "users"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
|
||||
@@ -36,6 +36,7 @@ clap_complete = "4.6.2"
|
||||
itertools = "0.14.0"
|
||||
tokio-util = "0.7.18"
|
||||
caps = "0.5.6"
|
||||
users = { version = "0.11.0", default-features = false }
|
||||
|
||||
[features]
|
||||
default = ["systemd"]
|
||||
|
||||
+2
-2
@@ -123,7 +123,7 @@ pub struct Args {
|
||||
#[arg(long, value_enum, hide = true)]
|
||||
completions: Option<Shell>,
|
||||
|
||||
users: Vec<String>,
|
||||
users: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
@@ -154,7 +154,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
println!("{}", serde_json::to_string_pretty(&reply).unwrap());
|
||||
} else {
|
||||
for user in reply {
|
||||
println!("{:#?}", user.unwrap());
|
||||
println!("{:#?}", user);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+49
-1
@@ -6,10 +6,58 @@ use std::{
|
||||
|
||||
use chrono::{DateTime, Duration, Timelike, Utc};
|
||||
use nix::sys::stat::stat;
|
||||
use users::all_users;
|
||||
use uucore::utmpx::Utmpx;
|
||||
|
||||
use crate::proto::finger_protocol::{FingerResponseUserEntry, FingerResponseUserSession};
|
||||
|
||||
/// Search for users whose username or full name contains the search string.
|
||||
pub fn search_for_user(
|
||||
search_string: &str,
|
||||
dont_search_fullnames: bool,
|
||||
) -> Vec<anyhow::Result<FingerResponseUserEntry>> {
|
||||
(unsafe { all_users() })
|
||||
.filter_map(|user| {
|
||||
let user = match nix::unistd::User::from_uid(user.uid().into()) {
|
||||
Ok(Some(user)) => user,
|
||||
Ok(None) => return None, // User disappeared, skip
|
||||
Err(e) => {
|
||||
return Some(Err(anyhow::anyhow!(
|
||||
"Failed to get user entry for UID {}: {}",
|
||||
user.uid(),
|
||||
e
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
let username = user.name;
|
||||
let full_name = String::from_utf8_lossy(
|
||||
&user
|
||||
.gecos
|
||||
.as_bytes()
|
||||
.split(|&b| b == b',')
|
||||
.next()
|
||||
.unwrap_or(&[]),
|
||||
)
|
||||
.to_string();
|
||||
|
||||
let matches_username = username.contains(search_string);
|
||||
let matches_fullname = !dont_search_fullnames && full_name.contains(search_string);
|
||||
if matches_username || matches_fullname {
|
||||
match get_local_user(&username) {
|
||||
Ok(Some(user_entry)) => Some(Ok(user_entry)),
|
||||
Ok(None) => None, // User exists but has .nofinger, skip
|
||||
Err(err) => Some(Err(err.into())),
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Helper function to read the content of a file if it exists and is readable,
|
||||
/// returning None if the file does not exist or is not readable.
|
||||
fn read_file_content_if_exists(path: &Path) -> anyhow::Result<Option<String>> {
|
||||
let file_is_readable = path.exists()
|
||||
&& path.is_file()
|
||||
@@ -35,7 +83,7 @@ fn read_file_content_if_exists(path: &Path) -> anyhow::Result<Option<String>> {
|
||||
/// Retrieve local user information for the given username.
|
||||
///
|
||||
/// Returns None if the user does not exist.
|
||||
pub fn get_local_user(username: &str) -> anyhow::Result<Option<FingerResponseUserEntry>> {
|
||||
fn get_local_user(username: &str) -> anyhow::Result<Option<FingerResponseUserEntry>> {
|
||||
let username = username.to_string();
|
||||
let user_entry = match nix::unistd::User::from_name(&username) {
|
||||
Ok(Some(user)) => user,
|
||||
|
||||
@@ -61,7 +61,7 @@ pub enum VarlinkRwhodClientError {
|
||||
pub trait VarlinkFingerClientProxy {
|
||||
async fn finger(
|
||||
&mut self,
|
||||
user_queries: Vec<String>,
|
||||
user_queries: Option<Vec<String>>,
|
||||
) -> zlink::Result<Result<VarlinkFingerResponse, VarlinkFingerClientError>>;
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ pub trait VarlinkFingerClientProxy {
|
||||
#[serde(tag = "method", content = "parameters")]
|
||||
pub enum VarlinkFingerClientRequest {
|
||||
#[serde(rename = "no.ntnu.pvv.roowho2.finger.Finger")]
|
||||
Finger { user_queries: Vec<String> },
|
||||
Finger { user_queries: Option<Vec<String>> },
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
@@ -78,7 +78,7 @@ pub enum VarlinkFingerClientResponse {
|
||||
Finger(VarlinkFingerResponse),
|
||||
}
|
||||
|
||||
pub type VarlinkFingerResponse = Vec<Option<FingerResponseUserEntry>>;
|
||||
pub type VarlinkFingerResponse = Vec<FingerResponseUserEntry>;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, ReplyError)]
|
||||
#[zlink(interface = "no.ntnu.pvv.roowho2.finger")]
|
||||
@@ -147,11 +147,37 @@ impl VarlinkRoowhoo2ClientServer {
|
||||
store.values().cloned().collect()
|
||||
}
|
||||
|
||||
async fn handle_finger_request(&self, user_queries: Vec<String>) -> VarlinkFingerResponse {
|
||||
user_queries
|
||||
.into_iter()
|
||||
.map(|username| fingerd::get_local_user(&username).unwrap())
|
||||
.collect()
|
||||
async fn handle_finger_request(
|
||||
&self,
|
||||
user_queries: Option<Vec<String>>,
|
||||
) -> VarlinkFingerResponse {
|
||||
match user_queries {
|
||||
// TODO: deduplicate results
|
||||
Some(usernames) => usernames
|
||||
.into_iter()
|
||||
.flat_map::<Vec<_>, _>(|username| {
|
||||
fingerd::search_for_user(&username, false)
|
||||
.into_iter()
|
||||
.map(|res| (username.clone(), res))
|
||||
.collect()
|
||||
})
|
||||
.filter_map(|(username, user)| match user {
|
||||
Ok(user_info) => Some(user_info),
|
||||
Err(err) => {
|
||||
tracing::error!(
|
||||
"Error retrieving local user information for '{}': {}",
|
||||
username,
|
||||
err
|
||||
);
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
None => {
|
||||
// TODO: fetch logged in users using utmp entries
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user