use std::{
env, io,
path::{Path, PathBuf},
process::Command,
};
const APP_EXECUTABLE: &str = "Contents/MacOS/FSK";
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum AuthorizationStatus {
NotInstalled,
NeedsApproval,
Authorized,
}
pub fn authorization_status() -> io::Result<AuthorizationStatus> {
let app = app_path();
if !app.exists() {
return Ok(AuthorizationStatus::NotInstalled);
}
let output = Command::new(app.join(APP_EXECUTABLE))
.arg("--fsk-authorization-status")
.output()?;
if !output.status.success() {
return Err(io::Error::other(
String::from_utf8_lossy(&output.stderr).trim().to_owned(),
));
}
match String::from_utf8_lossy(&output.stdout).trim() {
"authorized" => Ok(AuthorizationStatus::Authorized),
"needs-approval" => Ok(AuthorizationStatus::NeedsApproval),
"not-installed" => Ok(AuthorizationStatus::NotInstalled),
other => Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("unexpected FSK authorization response: {other}"),
)),
}
}
pub fn open_authorization_app() -> io::Result<()> {
let app = app_path();
if !app.exists() {
return Err(io::Error::new(
io::ErrorKind::NotFound,
format!("FSK.app was not found at {}", app.display()),
));
}
let status = Command::new("/usr/bin/open")
.arg("-a")
.arg(&app)
.arg("--args")
.arg("--fsk-authorize")
.status()?;
if status.success() {
Ok(())
} else {
Err(io::Error::other("macOS could not open FSK.app"))
}
}
fn app_path() -> PathBuf {
env::var_os("FSK_APP_PATH").map_or_else(
|| Path::new("/Applications/FSK.app").to_owned(),
PathBuf::from,
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn missing_app_is_not_installed() {
assert_eq!(
Path::new("/Applications/FSK.app").join(APP_EXECUTABLE),
Path::new("/Applications/FSK.app/Contents/MacOS/FSK")
);
}
}