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:
@@ -6,9 +6,11 @@ use crate::common::PIPESIZE;
|
||||
use crate::config;
|
||||
use crate::db;
|
||||
use crate::services::compression_service::CompressionService;
|
||||
use crate::services::meta_service::MetaService;
|
||||
use clap::Command;
|
||||
use log::debug;
|
||||
use rusqlite::Connection;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
/// Handles the update mode: modifies tags and metadata for an existing item by ID.
|
||||
///
|
||||
@@ -93,6 +95,13 @@ pub fn mode_update(
|
||||
db::set_item_tags(conn, item.clone(), tags)?;
|
||||
}
|
||||
|
||||
// Run meta plugins if --meta-plugin flags are provided
|
||||
let plugin_names = settings.meta_plugins_names();
|
||||
if !plugin_names.is_empty() {
|
||||
debug!("UPDATE: Running meta plugins: {:?}", plugin_names);
|
||||
run_meta_plugins_on_item(conn, cmd, settings, &data_path, &item, item_id)?;
|
||||
}
|
||||
|
||||
// Backfill size if not set
|
||||
let mut updated_item = item.clone();
|
||||
if item.size.is_none() {
|
||||
@@ -169,3 +178,59 @@ fn compute_item_size(data_path: &Path, item: &db::Item) -> Option<i64> {
|
||||
|
||||
Some(total_bytes)
|
||||
}
|
||||
|
||||
/// Runs meta plugins on an existing item's content and stores the results.
|
||||
fn run_meta_plugins_on_item(
|
||||
conn: &mut Connection,
|
||||
cmd: &mut Command,
|
||||
settings: &config::Settings,
|
||||
data_path: &Path,
|
||||
item: &db::Item,
|
||||
item_id: i64,
|
||||
) -> Result<()> {
|
||||
let mut item_path = data_path.to_path_buf();
|
||||
item_path.push(item_id.to_string());
|
||||
|
||||
if !item_path.exists() {
|
||||
debug!("UPDATE: Content file not found: {item_path:?}");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Collect metadata in memory
|
||||
let collected_meta: Arc<Mutex<Vec<(String, String)>>> = Arc::new(Mutex::new(Vec::new()));
|
||||
let collector = collected_meta.clone();
|
||||
let save_meta: crate::meta_plugin::SaveMetaFn =
|
||||
Arc::new(Mutex::new(move |name: &str, value: &str| {
|
||||
if let Ok(mut v) = collector.lock() {
|
||||
v.push((name.to_string(), value.to_string()));
|
||||
}
|
||||
}));
|
||||
|
||||
let meta_service = MetaService::new(save_meta);
|
||||
let mut plugins = meta_service.get_plugins(cmd, settings);
|
||||
|
||||
if plugins.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let compression_service = CompressionService::new();
|
||||
let mut reader = compression_service.stream_item_content(item_path, &item.compression)?;
|
||||
|
||||
meta_service.initialize_plugins(&mut plugins);
|
||||
|
||||
crate::common::stream_copy(&mut reader, |chunk| {
|
||||
meta_service.process_chunk(&mut plugins, chunk);
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
meta_service.finalize_plugins(&mut plugins);
|
||||
|
||||
// Write collected plugin metadata to DB
|
||||
if let Ok(entries) = collected_meta.lock() {
|
||||
for (name, value) in entries.iter() {
|
||||
db::add_meta(conn, item_id, name, value)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user