From 3de832d8868783e36e61b5c0ef814421d7bf2f55 Mon Sep 17 00:00:00 2001 From: Andrew Phillips Date: Tue, 29 Jul 2025 12:11:46 -0300 Subject: [PATCH] feat: add hostname and full_hostname meta plugins with error handling Co-authored-by: aider (openai/andrew.openrouter.qwen.qwen3-coder) --- Cargo.toml | 2 + src/meta_plugin.rs | 8 +++ src/meta_plugin/basic.rs | 110 +++++++++++++++++++++++++++++++++++++++ src/modes/save.rs | 14 +++-- 4 files changed, 130 insertions(+), 4 deletions(-) create mode 100644 src/meta_plugin/basic.rs diff --git a/Cargo.toml b/Cargo.toml index f6f9d70..117ef46 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,8 @@ flate2 = { version = "1.0.27", features = ["zlib-ng-compat"] } regex = "1.9.5" nix = "0.26.2" sha2 = "0.10.0" +local-ip-address = "0.5.5" +dns-lookup = "2.0.2" [dev-dependencies] tempfile = "3.3.0" diff --git a/src/meta_plugin.rs b/src/meta_plugin.rs index c734e30..b908315 100644 --- a/src/meta_plugin.rs +++ b/src/meta_plugin.rs @@ -9,9 +9,11 @@ use enum_map::{Enum, EnumMap}; pub mod program; pub mod digest; +pub mod basic; use crate::meta_plugin::program::MetaPluginProgram; use crate::meta_plugin::digest::{DigestSha256MetaPlugin, ReadTimeMetaPlugin, ReadRateMetaPlugin}; +use crate::meta_plugin::basic::{HostnameMetaPlugin, FullHostnameMetaPlugin}; #[derive(Debug, Eq, PartialEq, Clone, strum::EnumIter, strum::Display, strum::EnumString, Enum)] #[strum(ascii_case_insensitive)] @@ -21,6 +23,8 @@ pub enum MetaPluginType { DigestMd5, ReadTime, ReadRate, + Hostname, + FullHostname, } pub trait MetaPlugin { @@ -54,6 +58,8 @@ lazy_static! { } MetaPluginType::ReadTime => None, MetaPluginType::ReadRate => None, + MetaPluginType::Hostname => None, + MetaPluginType::FullHostname => None, }; } @@ -64,6 +70,8 @@ pub fn get_meta_plugin(meta_plugin_type: MetaPluginType) -> Box MetaPluginType::DigestMd5 => Box::new(MetaPluginProgram::new("md5sum", vec![], "digest_md5".to_string(), true)), MetaPluginType::ReadTime => Box::new(ReadTimeMetaPlugin::new()), MetaPluginType::ReadRate => Box::new(ReadRateMetaPlugin::new()), + MetaPluginType::Hostname => Box::new(HostnameMetaPlugin::new()), + MetaPluginType::FullHostname => Box::new(FullHostnameMetaPlugin::new()), } } diff --git a/src/meta_plugin/basic.rs b/src/meta_plugin/basic.rs new file mode 100644 index 0000000..9277312 --- /dev/null +++ b/src/meta_plugin/basic.rs @@ -0,0 +1,110 @@ +use anyhow::Result; +use gethostname::gethostname; +use std::io; +use std::io::Write; +use local_ip_address::local_ip; +use dns_lookup::lookup_addr; + +use crate::meta_plugin::MetaPlugin; + +#[derive(Debug, Clone, Default)] +pub struct HostnameMetaPlugin { + meta_name: String, +} + +impl HostnameMetaPlugin { + pub fn new() -> HostnameMetaPlugin { + HostnameMetaPlugin { + meta_name: "hostname".to_string(), + } + } +} + +impl MetaPlugin for HostnameMetaPlugin { + fn create(&self) -> Result> { + // For meta plugins, we don't actually create a writer since we're buffering data internally + Ok(Box::new(DummyWriter)) + } + + fn finalize(&mut self) -> io::Result { + match gethostname().into_string() { + Ok(hostname) => Ok(hostname), + Err(_) => Ok("unknown".to_string()), + } + } + + fn update(&mut self, _data: &[u8]) { + // No update needed for hostname + } + + fn meta_name(&mut self) -> String { + self.meta_name.clone() + } +} + +#[derive(Debug, Clone, Default)] +pub struct FullHostnameMetaPlugin { + meta_name: String, +} + +impl FullHostnameMetaPlugin { + pub fn new() -> FullHostnameMetaPlugin { + FullHostnameMetaPlugin { + meta_name: "full_hostname".to_string(), + } + } +} + +impl MetaPlugin for FullHostnameMetaPlugin { + fn create(&self) -> Result> { + // For meta plugins, we don't actually create a writer since we're buffering data internally + Ok(Box::new(DummyWriter)) + } + + fn finalize(&mut self) -> io::Result { + // Try to get the FQDN through reverse DNS lookup + match local_ip() { + Ok(my_local_ip) => { + match lookup_addr(&my_local_ip) { + Ok(hostname) => Ok(hostname), + Err(_) => { + // Fall back to regular hostname if reverse DNS fails + match gethostname().into_string() { + Ok(hostname) => Ok(hostname), + Err(_) => Ok("unknown".to_string()), + } + } + } + } + Err(_) => { + // Fall back to regular hostname if we can't get local IP + match gethostname().into_string() { + Ok(hostname) => Ok(hostname), + Err(_) => Ok("unknown".to_string()), + } + } + } + } + + fn update(&mut self, _data: &[u8]) { + // No update needed for full hostname + } + + fn meta_name(&mut self) -> String { + self.meta_name.clone() + } +} + +// Dummy writer that implements Write but doesn't do anything +// This is needed to satisfy the MetaPlugin trait requirements +struct DummyWriter; + +impl Write for DummyWriter { + fn write(&mut self, buf: &[u8]) -> io::Result { + Ok(buf.len()) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} diff --git a/src/modes/save.rs b/src/modes/save.rs index 45c0e6c..3539d11 100644 --- a/src/modes/save.rs +++ b/src/modes/save.rs @@ -172,10 +172,16 @@ pub fn mode_save( for meta_plugin in meta_plugins.iter_mut() { let meta_name = meta_plugin.meta_name(); - // TODO: Add error handling instead of unwrap. - let meta_value = meta_plugin.finalize().unwrap(); - - store_item_meta_value(conn, item.clone(), meta_name, meta_value)?; + match meta_plugin.finalize() { + Ok(meta_value) => { + if let Err(e) = store_item_meta_value(conn, item.clone(), meta_name, meta_value) { + eprintln!("Warning: Failed to store meta value for {}: {}", meta_name, e); + } + } + Err(e) => { + eprintln!("Warning: Failed to finalize meta plugin {}: {}", meta_name, e); + } + } } db::update_item(conn, item.clone())?;