rightkit_daemon/attach.rs
1//! Attach to the resident daemon, or fall back to running in process (rightkit#404).
2//!
3//! A shell that can work with or without a resident engine (the hub and the notch beside Pulse's
4//! engine) asks [`attach_or_fallback`]: when a live host of the service is discovered and answers,
5//! the result is [`Attached::Daemon`] with a connected [`Client`]; otherwise the caller's fallback
6//! runs in this process and its value is [`Attached::Fallback`]. Nothing is spawned here: starting
7//! a resident engine is [`DaemonLifecycle`](crate::DaemonLifecycle)'s job, under the spawn claim.
8//!
9//! A discovered host is only used when the connection succeeds, and with a
10//! [`HostExpectation`] only when the OS says the host is that program. A record that exists but
11//! cannot be read is "unknown", and is reported as [`Attached::Unknown`] without running the
12//! fallback, so a transient read failure never starts a second engine beside the resident one.
13
14use crate::client::{Client, ClientConfig};
15use crate::discovery::{try_adopt_or_spawn, AdoptOrSpawn};
16use crate::host_identity::HostExpectation;
17
18/// What [`attach_or_fallback`] did.
19#[allow(clippy::large_enum_variant)]
20pub enum Attached<T> {
21 /// A resident host answered; this client is connected to it.
22 Daemon(Client),
23 /// No usable resident host: the fallback ran in this process.
24 Fallback(T),
25 /// Whether a host exists could not be established (`service.json` exists and could not be
26 /// read). The fallback did not run; look again.
27 Unknown(std::io::Error),
28}
29
30/// Connect to the resident daemon described by `config`, or run `fallback`. `expect` pins the host
31/// to a program (see [`Client::connect_expecting`]); without it any same-user host is accepted.
32pub fn attach_or_fallback<T>(
33 config: &ClientConfig,
34 expect: Option<&HostExpectation>,
35 fallback: impl FnOnce() -> T,
36) -> Attached<T> {
37 let suite = match config.resolve_suite() {
38 Ok(suite) => suite,
39 Err(_) => return Attached::Fallback(fallback()),
40 };
41 match try_adopt_or_spawn(&suite, config.service()) {
42 Err(e) => Attached::Unknown(e),
43 Ok(AdoptOrSpawn::Spawn) => Attached::Fallback(fallback()),
44 Ok(AdoptOrSpawn::Adopt(_)) => {
45 let connected = match expect {
46 Some(expect) => Client::connect_expecting(config, expect).ok(),
47 None => Client::connect_config(config).ok(),
48 };
49 match connected {
50 Some(client) => Attached::Daemon(client),
51 None => Attached::Fallback(fallback()),
52 }
53 }
54 }
55}