use super::super::constants::{SESSION_ACCOUNT, SESSION_LABEL, SESSION_SERVICE};
use std::{
env,
io::Write,
process::{Command, Stdio},
};
pub(super) fn read_keychain_token() -> Option<String> {
if cfg!(target_os = "macos") {
let result = Command::new("security")
.args([
"find-generic-password",
"-s",
SESSION_SERVICE,
"-a",
SESSION_ACCOUNT,
"-w",
])
.stderr(Stdio::null())
.output();
return result
.ok()
.filter(|output| output.status.success())
.and_then(|output| non_empty_stdout_token(&output.stdout));
}
if cfg!(target_os = "linux") && has_command("secret-tool") {
let result = Command::new("secret-tool")
.args([
"lookup",
"service",
SESSION_SERVICE,
"account",
SESSION_ACCOUNT,
])
.stderr(Stdio::null())
.output();
return result
.ok()
.filter(|output| output.status.success())
.and_then(|output| non_empty_stdout_token(&output.stdout));
}
None
}
pub(super) fn write_keychain_token(token: &str) -> bool {
if cfg!(target_os = "macos") {
return Command::new("security")
.args([
"add-generic-password",
"-U",
"-s",
SESSION_SERVICE,
"-a",
SESSION_ACCOUNT,
"-l",
SESSION_LABEL,
"-w",
token,
])
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.is_ok_and(|status| status.success());
}
if cfg!(target_os = "linux") && has_command("secret-tool") {
let mut child = match Command::new("secret-tool")
.args([
"store",
"--label",
SESSION_LABEL,
"service",
SESSION_SERVICE,
"account",
SESSION_ACCOUNT,
])
.stdin(Stdio::piped())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
{
Ok(child) => child,
Err(_error) => return false,
};
if let Some(mut stdin) = child.stdin.take() {
if stdin.write_all(token.as_bytes()).is_err() {
return false;
}
}
return child.wait().is_ok_and(|status| status.success());
}
false
}
fn has_command(command: &str) -> bool {
env::var_os("PATH").is_some_and(|paths| {
env::split_paths(&paths).any(|directory| {
let candidate = directory.join(command);
if cfg!(windows) {
candidate.is_file() || directory.join(format!("{command}.exe")).is_file()
} else {
candidate.is_file()
}
})
})
}
fn non_empty_stdout_token(stdout: &[u8]) -> Option<String> {
let token = String::from_utf8_lossy(stdout).trim().to_owned();
if token.is_empty() { None } else { Some(token) }
}