//! Native flow-ui hosting policy. Hosting and attaching both converge on the
//! same HTTP client path; this module only decides which process owns root.
use crate::client::{health_answers, read_root_files, Endpoints};
use std::fs::OpenOptions;
use std::path::{Path, PathBuf};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum EmbedPolicy {
#[default]
Auto,
Never,
Always,
}
impl EmbedPolicy {
pub fn from_env() -> Self {
match std::env::var("FLOW_UI_FLOW_EMBED")
.unwrap_or_default()
.trim()
.to_ascii_lowercase()
.as_str()
{
"never" | "no" | "off" | "0" | "false" | "attach" | "client" => Self::Never,
"always" | "host" => Self::Always,
_ => Self::Auto,
}
}
}
pub fn embed_policy() -> EmbedPolicy {
EmbedPolicy::from_env()
}
pub fn default_root() -> PathBuf {
// Dev-only override: `FLOW_ROOT=
` points a test instance at an
// isolated root so it never touches the user's `~/.makepad/flow`.
if let Some(root) = std::env::var_os("FLOW_ROOT").filter(|value| !value.is_empty()) {
return PathBuf::from(root);
}
makepad_ai_hub::home::makepad_home().join("flow")
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Resolved {
/// A missing endpoint is intentional: attach-only sessions keep reading
/// `/listen` and retrying while another process starts.
Attach(
Option,
Option,
Option<[u8; 16]>,
),
Host,
}
pub fn resolve(policy: EmbedPolicy, root: impl AsRef, hint: Option) -> Resolved {
let root = root.as_ref();
let files = read_root_files(root);
let endpoints = hint.or(files.endpoints);
let attach = || Resolved::Attach(endpoints, files.token.clone(), files.server_id);
match policy {
EmbedPolicy::Never => attach(),
EmbedPolicy::Always => Resolved::Host,
EmbedPolicy::Auto => {
if endpoints.is_some_and(health_answers) || root_lock_held(root) {
attach()
} else {
Resolved::Host
}
}
}
}
fn root_lock_held(root: &Path) -> bool {
let Ok(file) = OpenOptions::new()
.write(true)
.open(root.join("server.lock"))
else {
return false;
};
match file.try_lock() {
Ok(()) => {
let _ = file.unlock();
false
}
Err(_) => true,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_root_hosts_in_auto_mode() {
let root = std::env::temp_dir().join(format!(
"makepad-flow-embed-empty-{}",
std::process::id()
));
let _ = std::fs::remove_dir_all(&root);
std::fs::create_dir_all(&root).unwrap();
assert_eq!(resolve(EmbedPolicy::Auto, &root, None), Resolved::Host);
std::fs::remove_dir_all(root).unwrap();
}
}