refactor: decouple meta plugins from DB via SaveMetaFn callback, extract shared utilities
- Add SaveMetaFn callback pattern: meta plugins receive a closure instead of
&Connection, enabling the same plugin code to work in local, client, and
server contexts (collect-to-Vec, collect-to-HashMap, or direct DB write)
- Client save now runs meta plugins locally during streaming (smart client
sets meta=false, server skips its own plugins)
- Add POST /api/item/{id}/update endpoint for re-running plugins on stored
content without downloading compressed data
- Add client update mode (--update with --meta-plugin flags)
- Extract shared utilities: stream_copy, print_serialized, build_path_table,
ensure_default_tag to reduce duplication across modes
- Add upsert_tag for idempotent tag addition (INSERT OR IGNORE)
- Add warn logging on save_meta lock failure in BaseMetaPlugin and MetaService
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
use log::debug;
|
||||
use log::{debug, warn};
|
||||
use once_cell::sync::Lazy;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Mutex;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
pub mod cwd;
|
||||
pub mod digest;
|
||||
@@ -61,8 +61,16 @@ pub struct MetaPluginResponse {
|
||||
pub is_finalized: bool,
|
||||
}
|
||||
|
||||
/// Type alias for the save_meta callback shared by all plugins.
|
||||
pub type SaveMetaFn = Arc<Mutex<dyn FnMut(&str, &str) + Send>>;
|
||||
|
||||
/// Creates a no-op save_meta for plugins not wired through MetaService.
|
||||
pub fn noop_save_meta() -> SaveMetaFn {
|
||||
Arc::new(Mutex::new(|_: &str, _: &str| {}))
|
||||
}
|
||||
|
||||
/// Base implementation for meta plugins to reduce boilerplate.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[derive(Clone)]
|
||||
pub struct BaseMetaPlugin {
|
||||
/// Output mappings for metadata.
|
||||
pub outputs: std::collections::HashMap<String, serde_yaml::Value>,
|
||||
@@ -70,6 +78,29 @@ pub struct BaseMetaPlugin {
|
||||
pub options: std::collections::HashMap<String, serde_yaml::Value>,
|
||||
/// Whether the plugin is finalized.
|
||||
pub is_finalized: bool,
|
||||
/// Callback to store metadata. Called directly by plugins.
|
||||
pub save_meta: SaveMetaFn,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for BaseMetaPlugin {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("BaseMetaPlugin")
|
||||
.field("outputs", &self.outputs)
|
||||
.field("options", &self.options)
|
||||
.field("is_finalized", &self.is_finalized)
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for BaseMetaPlugin {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
outputs: HashMap::new(),
|
||||
options: HashMap::new(),
|
||||
is_finalized: false,
|
||||
save_meta: noop_save_meta(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BaseMetaPlugin {
|
||||
@@ -83,41 +114,39 @@ impl BaseMetaPlugin {
|
||||
}
|
||||
|
||||
/// Returns a reference to the outputs mapping.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A reference to the `HashMap` of outputs.
|
||||
pub fn outputs(&self) -> &std::collections::HashMap<String, serde_yaml::Value> {
|
||||
&self.outputs
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the outputs mapping.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A mutable reference to the `HashMap` of outputs.
|
||||
pub fn outputs_mut(&mut self) -> &mut std::collections::HashMap<String, serde_yaml::Value> {
|
||||
&mut self.outputs
|
||||
}
|
||||
|
||||
/// Returns a reference to the options mapping.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A reference to the `HashMap` of options.
|
||||
pub fn options(&self) -> &std::collections::HashMap<String, serde_yaml::Value> {
|
||||
&self.options
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the options mapping.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A mutable reference to the `HashMap` of options.
|
||||
pub fn options_mut(&mut self) -> &mut std::collections::HashMap<String, serde_yaml::Value> {
|
||||
&mut self.options
|
||||
}
|
||||
|
||||
/// Sets the save_meta callback on the base plugin.
|
||||
pub fn set_save_meta(&mut self, save_meta: SaveMetaFn) {
|
||||
self.save_meta = save_meta;
|
||||
}
|
||||
|
||||
/// Saves a metadata entry via the save_meta callback.
|
||||
pub fn save_meta(&self, name: &str, value: &str) {
|
||||
if let Ok(mut f) = self.save_meta.lock() {
|
||||
f(name, value);
|
||||
} else {
|
||||
warn!("META_PLUGIN: save_meta lock poisoned, dropping metadata: {name}={value}");
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function to initialize plugin options and outputs.
|
||||
///
|
||||
/// # Arguments
|
||||
@@ -566,6 +595,16 @@ where
|
||||
{
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the save_meta callback for this plugin.
|
||||
///
|
||||
/// Called by MetaService to wire the plugin to the metadata storage.
|
||||
fn set_save_meta(&mut self, _save_meta: SaveMetaFn) {}
|
||||
|
||||
/// Saves a metadata entry via the save_meta callback.
|
||||
///
|
||||
/// Plugins call this during initialize/update/finalize to persist metadata.
|
||||
fn save_meta(&self, _name: &str, _value: &str) {}
|
||||
}
|
||||
|
||||
/// Global registry for meta plugins.
|
||||
@@ -593,12 +632,29 @@ pub fn get_meta_plugin(
|
||||
meta_plugin_type: MetaPluginType,
|
||||
options: Option<std::collections::HashMap<String, serde_yaml::Value>>,
|
||||
outputs: Option<std::collections::HashMap<String, serde_yaml::Value>>,
|
||||
) -> anyhow::Result<Box<dyn MetaPlugin>> {
|
||||
get_meta_plugin_with_save(meta_plugin_type, options, outputs, None)
|
||||
}
|
||||
|
||||
/// Creates a meta plugin instance with an optional save_meta callback.
|
||||
///
|
||||
/// If `save_meta` is provided, it is wired to the plugin so it can
|
||||
/// store metadata directly during initialize/update/finalize.
|
||||
pub fn get_meta_plugin_with_save(
|
||||
meta_plugin_type: MetaPluginType,
|
||||
options: Option<std::collections::HashMap<String, serde_yaml::Value>>,
|
||||
outputs: Option<std::collections::HashMap<String, serde_yaml::Value>>,
|
||||
save_meta: Option<SaveMetaFn>,
|
||||
) -> anyhow::Result<Box<dyn MetaPlugin>> {
|
||||
let registry = META_PLUGIN_REGISTRY
|
||||
.lock()
|
||||
.map_err(|e| anyhow::anyhow!("plugin registry poisoned: {e}"))?;
|
||||
if let Some(constructor) = registry.get(&meta_plugin_type) {
|
||||
return Ok(constructor(options, outputs));
|
||||
let mut plugin = constructor(options, outputs);
|
||||
if let Some(sm) = save_meta {
|
||||
plugin.set_save_meta(sm);
|
||||
}
|
||||
return Ok(plugin);
|
||||
}
|
||||
|
||||
anyhow::bail!("Meta plugin {meta_plugin_type:?} not registered")
|
||||
|
||||
Reference in New Issue
Block a user