Files
keep/src/services/meta_service.rs
Andrew Phillips 9b7cbd5244 fix: resolve doctest failures, database bugs, and remove dead code
- Fix all 96 doctest failures across 20 files by adding hidden imports and
  proper test setup (68 pass, 33 intentionally ignored)
- Fix set_item_tags: wrap in transaction and replace item.id.unwrap() with
  proper error handling
- Fix get_items_matching: replace N+1 per-item meta queries with batch
  get_meta_for_items() call
- Fix get_item_matching: apply meta filtering instead of ignoring the parameter
- Remove duplicate doc comment in store_meta
- Remove dead code files: plugin.rs, plugins.rs, binary_detection.rs
  (never declared as modules)
- Apply cargo fmt formatting fixes
- Add keep.db to .gitignore
2026-03-12 11:58:44 -03:00

229 lines
8.0 KiB
Rust

use crate::config::Settings;
use crate::meta_plugin::{MetaPlugin, MetaPluginType};
use crate::modes::common::settings_meta_plugin_types;
use clap::Command;
use log::debug;
use rusqlite::Connection;
use std::collections::HashMap;
pub struct MetaService;
impl MetaService {
pub fn new() -> Self {
Self
}
pub fn get_plugins(&self, cmd: &mut Command, settings: &Settings) -> Vec<Box<dyn MetaPlugin>> {
debug!("META_SERVICE: get_plugins called");
let meta_plugin_types: Vec<MetaPluginType> = settings_meta_plugin_types(cmd, settings);
debug!("META_SERVICE: Meta plugin types from settings: {meta_plugin_types:?}");
// Create plugins with their configuration
let meta_plugins: Vec<Box<dyn MetaPlugin>> = meta_plugin_types
.iter()
.map(|meta_plugin_type| {
debug!("META_SERVICE: Creating plugin: {meta_plugin_type:?}");
// Get the plugin name using strum's Display implementation
let plugin_name = meta_plugin_type.to_string();
// Get options and outputs from settings
let (options, outputs) = if let Some(meta_plugin_configs) = &settings.meta_plugins {
if let Some(config) = meta_plugin_configs.iter().find(|c| c.name == plugin_name)
{
// Convert options and outputs to the appropriate types
let options: std::collections::HashMap<String, serde_yaml::Value> = config
.options
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect();
let outputs: std::collections::HashMap<String, serde_yaml::Value> = config
.outputs
.iter()
.map(|(k, v)| (k.clone(), serde_yaml::Value::String(v.clone())))
.collect();
(Some(options), Some(outputs))
} else {
(None, None)
}
} else {
(None, None)
};
crate::meta_plugin::get_meta_plugin(meta_plugin_type.clone(), options, outputs)
})
.collect();
meta_plugins
}
pub fn initialize_plugins(
&self,
plugins: &mut [Box<dyn MetaPlugin>],
conn: &Connection,
item_id: i64,
) {
// Check for duplicate output names before initializing plugins
let mut output_names: std::collections::HashMap<String, Vec<String>> =
std::collections::HashMap::new();
for plugin in plugins.iter() {
let plugin_name = plugin.meta_type().to_string();
// For each plugin, collect all the output names it might write to
for (internal_name, output_config) in plugin.outputs() {
let output_name = match output_config {
serde_yaml::Value::String(remapped_name) => remapped_name.clone(),
serde_yaml::Value::Bool(true) => internal_name.clone(),
serde_yaml::Value::Bool(false) => continue, // This output is disabled
_ => internal_name.clone(), // Default to internal name for other types
};
// Only track outputs that will actually be written
if !matches!(output_config, serde_yaml::Value::Bool(false)) {
output_names
.entry(output_name)
.or_default()
.push(plugin_name.clone());
}
}
}
// Print warnings for duplicate output names
for (output_name, plugin_names) in &output_names {
if plugin_names.len() > 1 {
log::warn!(
"META_SERVICE: Output name '{}' is provided by multiple plugins: {}",
output_name,
plugin_names.join(", ")
);
}
}
for meta_plugin in plugins.iter_mut() {
let response = meta_plugin.initialize();
self.process_plugin_response(conn, item_id, &mut **meta_plugin, response);
}
}
pub fn process_chunk(
&self,
plugins: &mut [Box<dyn MetaPlugin>],
chunk: &[u8],
conn: &Connection,
item_id: i64,
) {
for meta_plugin in plugins.iter_mut() {
// Skip plugins that are already finalized
if meta_plugin.is_finalized() {
continue;
}
let response = meta_plugin.update(chunk);
self.process_plugin_response(conn, item_id, &mut **meta_plugin, response.clone());
// Set finalized flag if response indicates finalization
if response.is_finalized {
meta_plugin.set_finalized(true);
}
}
}
pub fn finalize_plugins(
&self,
plugins: &mut [Box<dyn MetaPlugin>],
conn: &Connection,
item_id: i64,
) {
for meta_plugin in plugins.iter_mut() {
// Skip plugins that are already finalized
if meta_plugin.is_finalized() {
continue;
}
let response = meta_plugin.finalize();
self.process_plugin_response(conn, item_id, &mut **meta_plugin, response.clone());
// Set finalized flag if response indicates finalization
if response.is_finalized {
meta_plugin.set_finalized(true);
}
}
}
/// Internal helper to process a meta plugin response and store metadata.
///
/// Iterates over the metadata entries in the response and stores each in the database
/// using `store_meta`. Logs warnings if storage fails.
///
/// # Arguments
///
/// * `conn` - Database connection.
/// * `item_id` - Item ID to associate with the metadata.
/// * `_plugin` - Reference to the plugin (unused).
/// * `response` - The plugin response containing metadata.
///
/// # Errors
///
/// Logs warnings for individual storage failures but does not return errors.
fn process_plugin_response(
&self,
conn: &Connection,
item_id: i64,
_plugin: &mut dyn MetaPlugin,
response: crate::meta_plugin::MetaPluginResponse,
) {
for meta_data in response.metadata {
// The metadata has already been processed by the plugin, so we can use it directly
// Save to database
let db_meta = crate::db::Meta {
id: item_id,
name: meta_data.name,
value: meta_data.value,
};
if let Err(e) = crate::db::store_meta(conn, db_meta) {
log::warn!("META_SERVICE: Failed to store metadata: {e}");
}
}
}
/// Collects initial metadata from environment variables and hostname.
///
/// Gathers metadata from `KEEP_META_*` environment variables and adds hostname
/// if not already present.
///
/// # Returns
///
/// A `HashMap` of initial metadata key-value pairs.
///
/// # Examples
///
/// ```
/// # use keep::services::MetaService;
/// let service = MetaService::new();
/// let initial_meta = service.collect_initial_meta();
/// ```
pub fn collect_initial_meta(&self) -> HashMap<String, String> {
let mut item_meta: HashMap<String, String> = crate::modes::common::get_meta_from_env();
if let Ok(hostname) = gethostname::gethostname().into_string()
&& !item_meta.contains_key("hostname")
{
item_meta.insert("hostname".to_string(), hostname);
}
item_meta
}
}
impl Default for MetaService {
/// Provides a default `MetaService` instance.
///
/// # Returns
///
/// A new `MetaService` via `new()`.
fn default() -> Self {
Self::new()
}
}