refactor: harden internal daemon entrypoint and cleanup observations
Remove the internal daemon subcommand from the public CLI surface, start the daemon via an internal env trigger, and ensure generated completions/help never expose internal entrypoints. Also finish the pending observation cleanups and docs updates, including config/key deduplication, registry matching cleanup, and remaining roadmap/project map staleness fixes.
This commit is contained in:
+20
-8
@@ -84,21 +84,37 @@ impl TvctlConfig {
|
||||
|
||||
/// Return one flattened config value by stable key.
|
||||
pub fn get_value(&self, key: &str) -> Option<String> {
|
||||
self.entries().remove(key)
|
||||
match key {
|
||||
"daemon.socket" => Some(self.daemon.socket.clone()),
|
||||
"daemon.http_enabled" => Some(self.daemon.http_enabled.to_string()),
|
||||
"daemon.http_port" => Some(self.daemon.http_port.to_string()),
|
||||
"daemon.http_host" => Some(self.daemon.http_host.clone()),
|
||||
"daemon.log_level" => Some(self.daemon.log_level.clone()),
|
||||
"discovery.auto_discover" => Some(self.discovery.auto_discover.to_string()),
|
||||
"discovery.interval_secs" => Some(self.discovery.interval_secs.to_string()),
|
||||
"discovery.timeout_secs" => Some(self.discovery.timeout_secs.to_string()),
|
||||
"devices.default" => Some(self.devices.default.clone()),
|
||||
"remote.roku_key_mode" => Some(self.remote.roku_key_mode.clone()),
|
||||
"remote.roku_press_duration_ms" => Some(self.remote.roku_press_duration_ms.to_string()),
|
||||
"dev.enabled" => Some(self.dev.enabled.to_string()),
|
||||
"dev.roku_username" => Some(self.dev.roku_username.clone()),
|
||||
"dev.roku_password" => Some(self.dev.roku_password.clone()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set one flattened config value by stable key.
|
||||
pub fn set_value(&mut self, key: &str, value: &str) -> anyhow::Result<()> {
|
||||
match key {
|
||||
"daemon.socket" => self.daemon.socket = value.to_string(),
|
||||
"daemon.http_enabled" => self.daemon.http_enabled = parse_bool(key, value)?,
|
||||
"daemon.http_enabled" => self.daemon.http_enabled = parse_value(key, value)?,
|
||||
"daemon.http_port" => self.daemon.http_port = parse_value(key, value)?,
|
||||
"daemon.http_host" => self.daemon.http_host = value.to_string(),
|
||||
"daemon.log_level" => {
|
||||
validate_log_level(value)?;
|
||||
self.daemon.log_level = value.to_string();
|
||||
}
|
||||
"discovery.auto_discover" => self.discovery.auto_discover = parse_bool(key, value)?,
|
||||
"discovery.auto_discover" => self.discovery.auto_discover = parse_value(key, value)?,
|
||||
"discovery.interval_secs" => self.discovery.interval_secs = parse_value(key, value)?,
|
||||
"discovery.timeout_secs" => self.discovery.timeout_secs = parse_value(key, value)?,
|
||||
"devices.default" => self.devices.default = value.to_string(),
|
||||
@@ -106,7 +122,7 @@ impl TvctlConfig {
|
||||
"remote.roku_press_duration_ms" => {
|
||||
self.remote.roku_press_duration_ms = parse_value(key, value)?
|
||||
}
|
||||
"dev.enabled" => self.dev.enabled = parse_bool(key, value)?,
|
||||
"dev.enabled" => self.dev.enabled = parse_value(key, value)?,
|
||||
"dev.roku_username" => self.dev.roku_username = value.to_string(),
|
||||
"dev.roku_password" => self.dev.roku_password = value.to_string(),
|
||||
other => bail!("unknown config key '{other}'"),
|
||||
@@ -301,10 +317,6 @@ fn current_uid() -> u32 {
|
||||
unsafe { libc::geteuid() }
|
||||
}
|
||||
|
||||
fn parse_bool(key: &str, value: &str) -> anyhow::Result<bool> {
|
||||
parse_value(key, value)
|
||||
}
|
||||
|
||||
fn parse_value<T>(key: &str, value: &str) -> anyhow::Result<T>
|
||||
where
|
||||
T: std::str::FromStr,
|
||||
|
||||
+3
-40
@@ -34,11 +34,12 @@ use tracing::warn;
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
use crate::adapters::{Device, TvKey};
|
||||
use crate::adapters::Device;
|
||||
use crate::api;
|
||||
use crate::logging;
|
||||
|
||||
pub type SharedDaemon = Arc<Mutex<Daemon>>;
|
||||
pub const INTERNAL_DAEMON_ENV: &str = "TVCTL_INTERNAL_DAEMON";
|
||||
|
||||
/// The long-lived tvctld process.
|
||||
#[derive(Debug)]
|
||||
@@ -644,7 +645,7 @@ pub(crate) async fn execute_request(
|
||||
Ok(device) => device,
|
||||
Err(response) => return (response, false),
|
||||
};
|
||||
let detail = format!("Sent key '{}' to {}.", format_tv_key(&key), device.name);
|
||||
let detail = format!("Sent key '{}' to {}.", key.normalized_name(), device.name);
|
||||
match guard.adapters.key(&device, key).await {
|
||||
Ok(()) => (action_success(device, detail), false),
|
||||
Err(error) => (
|
||||
@@ -834,44 +835,6 @@ async fn send_key_sequence(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn format_tv_key(key: &TvKey) -> String {
|
||||
match key {
|
||||
TvKey::Home => "home".to_string(),
|
||||
TvKey::Back => "back".to_string(),
|
||||
TvKey::Up => "up".to_string(),
|
||||
TvKey::Down => "down".to_string(),
|
||||
TvKey::Left => "left".to_string(),
|
||||
TvKey::Right => "right".to_string(),
|
||||
TvKey::Select => "select".to_string(),
|
||||
TvKey::Play => "play".to_string(),
|
||||
TvKey::Pause => "pause".to_string(),
|
||||
TvKey::PlayPause => "play-pause".to_string(),
|
||||
TvKey::Stop => "stop".to_string(),
|
||||
TvKey::Rewind => "rewind".to_string(),
|
||||
TvKey::FastForward => "fast-forward".to_string(),
|
||||
TvKey::Replay => "replay".to_string(),
|
||||
TvKey::Skip => "skip".to_string(),
|
||||
TvKey::ChannelUp => "channel-up".to_string(),
|
||||
TvKey::ChannelDown => "channel-down".to_string(),
|
||||
TvKey::VolumeUp => "volume-up".to_string(),
|
||||
TvKey::VolumeDown => "volume-down".to_string(),
|
||||
TvKey::Mute => "mute".to_string(),
|
||||
TvKey::Power => "power".to_string(),
|
||||
TvKey::PowerOn => "power-on".to_string(),
|
||||
TvKey::PowerOff => "power-off".to_string(),
|
||||
TvKey::InputHdmi1 => "input-hdmi1".to_string(),
|
||||
TvKey::InputHdmi2 => "input-hdmi2".to_string(),
|
||||
TvKey::InputHdmi3 => "input-hdmi3".to_string(),
|
||||
TvKey::InputHdmi4 => "input-hdmi4".to_string(),
|
||||
TvKey::InputAv => "input-av".to_string(),
|
||||
TvKey::InputTuner => "input-tuner".to_string(),
|
||||
TvKey::Search => "search".to_string(),
|
||||
TvKey::Info => "info".to_string(),
|
||||
TvKey::Options => "options".to_string(),
|
||||
TvKey::Literal(text) => format!("literal:{text}"),
|
||||
}
|
||||
}
|
||||
|
||||
async fn run_discovery(daemon: &mut Daemon) -> anyhow::Result<Vec<Device>> {
|
||||
let discovery = daemon.discovery.clone();
|
||||
let devices = discovery.discover_all(&mut daemon.registry).await?;
|
||||
|
||||
+2
-11
@@ -97,10 +97,8 @@ impl DeviceRegistry {
|
||||
|
||||
/// Remove a device by UUID or case-insensitive name.
|
||||
pub fn remove(&mut self, target: &str) -> Option<Device> {
|
||||
let index = self
|
||||
.devices
|
||||
.iter()
|
||||
.position(|device| matches_target(device, target))?;
|
||||
let id = self.find(target)?.id;
|
||||
let index = self.devices.iter().position(|device| device.id == id)?;
|
||||
let removed = self.devices.remove(index);
|
||||
self.ensure_default();
|
||||
Some(removed)
|
||||
@@ -319,13 +317,6 @@ impl AdapterRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
fn matches_target(device: &Device, target: &str) -> bool {
|
||||
let target_uuid = Uuid::parse_str(target).ok();
|
||||
let normalized = target.to_ascii_lowercase();
|
||||
target_uuid.map(|uuid| device.id == uuid).unwrap_or(false)
|
||||
|| device.name.to_ascii_lowercase() == normalized
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
Reference in New Issue
Block a user