feat: implement unified settings system

Co-authored-by: aider (openai/andrew/openrouter/anthropic/claude-sonnet-4) <aider@aider.chat>
This commit is contained in:
Andrew Phillips
2025-08-15 16:36:58 -03:00
parent 067cba703b
commit 56f4d8aad5
12 changed files with 283 additions and 140 deletions

View File

@@ -3,15 +3,21 @@ use std::fs;
use anyhow::{Result, Context};
use serde::{Deserialize, Serialize};
use log::debug;
use crate::args::{Args, KeyValue};
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
pub struct Config {
pub dir: Option<PathBuf>,
pub list_format: Option<String>,
pub human_readable: Option<bool>,
pub output_format: Option<String>,
pub verbose: Option<u8>,
pub quiet: Option<bool>,
pub force: Option<bool>,
pub server: Option<ServerConfig>,
pub compression_plugin: Option<CompressionPluginConfig>,
pub meta_plugins: Option<Vec<MetaPluginConfig>>,
pub digest: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
@@ -31,6 +37,110 @@ pub struct MetaPluginConfig {
pub name: String,
}
/// Unified settings that merges config file and CLI arguments
#[derive(Debug, Clone)]
pub struct Settings {
pub dir: PathBuf,
pub list_format: String,
pub human_readable: bool,
pub output_format: Option<String>,
pub verbose: u8,
pub quiet: bool,
pub force: bool,
pub server_password: Option<String>,
pub compression: Option<String>,
pub digest: Option<String>,
pub meta_plugins: Vec<String>,
pub meta: Vec<KeyValue>,
}
impl Settings {
/// Create unified settings from config and args with proper priority
pub fn from_config_and_args(config: &Config, args: &Args, default_dir: PathBuf) -> Result<Self> {
// Apply priority: CLI args > env vars > config file > defaults
let dir = args.options.dir.clone()
.or_else(|| config.dir.clone())
.unwrap_or(default_dir);
let list_format = if args.options.list_format != "id,time,size,tags,meta:hostname" {
args.options.list_format.clone()
} else {
config.list_format.clone()
.unwrap_or_else(|| "id,time,size,tags,meta:hostname".to_string())
};
let human_readable = args.options.human_readable || config.human_readable.unwrap_or(false);
let output_format = args.options.output_format.clone()
.or_else(|| config.output_format.clone());
let verbose = if args.options.verbose > 0 {
args.options.verbose
} else {
config.verbose.unwrap_or(0)
};
let quiet = args.options.quiet || config.quiet.unwrap_or(false);
let force = args.options.force || config.force.unwrap_or(false);
let server_password = args.options.server_password.clone()
.or_else(|| config.get_server_password().ok().flatten());
let compression = args.item.compression.clone()
.or_else(|| config.compression_plugin.as_ref().map(|c| c.name.clone()));
let digest = args.item.digest.clone()
.or_else(|| config.digest.clone());
let meta_plugins = if !args.item.meta_plugins.is_empty() {
args.item.meta_plugins.clone()
} else {
config.meta_plugins.as_ref()
.map(|plugins| plugins.iter().map(|p| p.name.clone()).collect())
.unwrap_or_default()
};
Ok(Settings {
dir,
list_format,
human_readable,
output_format,
verbose,
quiet,
force,
server_password,
compression,
digest,
meta_plugins,
meta: args.item.meta.clone(),
})
}
/// Get server address from args or config
pub fn get_server_address(&self, args: &Args, config: &Config) -> Option<String> {
// CLI args take priority
if let Some(server_addr) = &args.mode.server {
return Some(server_addr.clone());
}
// Then config file
if let Some(server_config) = &config.server {
let mut addr = server_config.address.clone().unwrap_or_else(|| "127.0.0.1".to_string());
if let Some(port) = server_config.port {
if !addr.contains(':') {
addr.push_str(&format!(":{}", port));
}
} else if !addr.contains(':') {
addr.push_str(":8080");
}
return Some(addr);
}
None
}
}
impl Config {
/// Load configuration from a file
pub fn from_file(path: &PathBuf) -> Result<Self> {

View File

@@ -29,7 +29,7 @@ extern crate serde_yaml;
extern crate serde;
use args::{Args, NumberOrString};
use config::Config;
use config::{Config, Settings};
/**
* Main function to handle command-line arguments and execute the appropriate mode.
@@ -67,6 +67,16 @@ fn main() -> Result<(), Error> {
debug!("MAIN: Loaded config: {:?}", config);
// Determine default data directory
let default_dir = match proj_dirs {
Some(ref proj_dirs) => proj_dirs.data_dir().to_path_buf(),
None => return Err(anyhow!("Unable to determine data directory")),
};
// Create unified settings
let settings = Settings::from_config_and_args(&config, &args, default_dir)?;
debug!("MAIN: Unified settings: {:?}", settings);
let ids = &mut Vec::new();
let tags = &mut Vec::new();
@@ -114,23 +124,9 @@ fn main() -> Result<(), Error> {
mode = KeepModes::Status;
} else if args.mode.server.is_some() {
mode = KeepModes::Server;
} else if config.server.is_some() && args.mode.server.is_none() {
} else if settings.get_server_address(&args, &config).is_some() && args.mode.server.is_none() {
// If server is configured in config file but not specified via CLI
if let Some(server_config) = &config.server {
let mut server_addr = String::new();
if let Some(address) = &server_config.address {
server_addr.push_str(address);
} else {
server_addr.push_str("127.0.0.1");
}
if let Some(port) = server_config.port {
server_addr.push_str(&format!(":{}", port));
} else {
server_addr.push_str(":8080");
}
args.mode.server = Some(server_addr);
mode = KeepModes::Server;
}
mode = KeepModes::Server;
}
if mode == KeepModes::Unknown {
@@ -142,7 +138,7 @@ fn main() -> Result<(), Error> {
}
// Validate output format usage
if let Some(output_format_str) = &args.options.output_format {
if let Some(output_format_str) = &settings.output_format {
if output_format_str != "table" && mode != KeepModes::Info && mode != KeepModes::Status && mode != KeepModes::List {
cmd.error(
ErrorKind::InvalidValue,
@@ -152,7 +148,7 @@ fn main() -> Result<(), Error> {
}
// Validate human-readable usage
if args.options.human_readable && mode != KeepModes::List && mode != KeepModes::Info {
if settings.human_readable && mode != KeepModes::List && mode != KeepModes::Info {
cmd.error(
ErrorKind::InvalidValue,
"--human-readable can only be used with --list and --info modes"
@@ -160,7 +156,7 @@ fn main() -> Result<(), Error> {
}
// Validate server password usage
if args.options.server_password.is_some() && mode != KeepModes::Server {
if settings.server_password.is_some() && mode != KeepModes::Server {
cmd.error(
ErrorKind::InvalidValue,
"--server-password can only be used with --server mode"
@@ -172,58 +168,11 @@ fn main() -> Result<(), Error> {
debug!("MAIN: tags: {:?}", tags);
debug!("MAIN: mode: {:?}", mode);
// Apply configuration priority: CLI args > env vars > config file > defaults
if args.options.dir.is_none() {
if let Some(config_dir) = &config.dir {
args.options.dir = Some(config_dir.clone());
} else {
match proj_dirs {
Some(proj_dirs) => args.options.dir = Some(proj_dirs.data_dir().to_path_buf()),
None => return Err(anyhow!("Unable to determine data directory")),
}
}
}
// Apply list_format from config if not set via CLI/env
if args.options.list_format == "id,time,size,tags,meta:hostname" {
if let Some(config_list_format) = &config.list_format {
args.options.list_format = config_list_format.clone();
}
}
// Apply human_readable from config if not set via CLI
if !args.options.human_readable {
if let Some(config_human_readable) = config.human_readable {
args.options.human_readable = config_human_readable;
}
}
// Apply server password from config file if not set via CLI/env
if args.options.server_password.is_none() {
if let Ok(Some(password)) = config.get_server_password() {
args.options.server_password = Some(password);
}
}
// Apply compression from config if not set via CLI/env
if args.item.compression.is_none() {
if let Some(compression_plugin) = &config.compression_plugin {
args.item.compression = Some(compression_plugin.name.clone());
}
}
// Apply meta_plugins from config if not set via CLI/env
if args.item.meta_plugins.is_empty() {
if let Some(meta_plugins) = &config.meta_plugins {
args.item.meta_plugins = meta_plugins.iter().map(|p| p.name.clone()).collect();
}
}
unsafe {
libc::umask(0o077);
}
let data_path = args.options.dir.clone().unwrap();
let data_path = settings.dir.clone();
let mut db_path = data_path.clone();
db_path.push("keep-1.db");
@@ -238,31 +187,31 @@ fn main() -> Result<(), Error> {
match mode {
KeepModes::Save => {
crate::modes::save::mode_save(&mut cmd, &args, ids, tags, &mut conn, data_path)?
crate::modes::save::mode_save(&mut cmd, &settings, &config, ids, tags, &mut conn, data_path)?
}
KeepModes::Get => {
crate::modes::get::mode_get(&mut cmd, &args, ids, tags, &mut conn, data_path)?
crate::modes::get::mode_get(&mut cmd, &settings, &config, ids, tags, &mut conn, data_path)?
}
KeepModes::Diff => {
crate::modes::diff::mode_diff(&mut cmd, &args, ids, tags, &mut conn, data_path)?
crate::modes::diff::mode_diff(&mut cmd, &settings, &config, ids, tags, &mut conn, data_path)?
}
KeepModes::List => {
crate::modes::list::mode_list(&mut cmd, &args, ids, tags, &mut conn, data_path)?
crate::modes::list::mode_list(&mut cmd, &settings, &config, ids, tags, &mut conn, data_path)?
}
KeepModes::Update => {
crate::modes::update::mode_update(&mut cmd, &args, ids, tags, &mut conn, data_path)?
crate::modes::update::mode_update(&mut cmd, &settings, &config, ids, tags, &mut conn, data_path)?
}
KeepModes::Info => {
crate::modes::info::mode_info(&mut cmd, &args, ids, tags, &mut conn, data_path)?
crate::modes::info::mode_info(&mut cmd, &settings, &config, ids, tags, &mut conn, data_path)?
}
KeepModes::Delete => {
crate::modes::delete::mode_delete(&mut cmd, &args, ids, tags, &mut conn, data_path)?
crate::modes::delete::mode_delete(&mut cmd, &settings, &config, ids, tags, &mut conn, data_path)?
}
KeepModes::Status => {
crate::modes::status::mode_status(&mut cmd, &args, data_path, db_path)?
crate::modes::status::mode_status(&mut cmd, &settings, &config, data_path, db_path)?
}
KeepModes::Server => {
crate::modes::server::mode_server(&mut cmd, &args, &mut conn, data_path)?
crate::modes::server::mode_server(&mut cmd, &settings, &config, &mut conn, data_path)?
}
KeepModes::Unknown => todo!(),
}

View File

@@ -157,12 +157,6 @@ impl FromStr for OutputFormat {
}
}
pub fn get_output_format(args: &Args) -> OutputFormat {
args.options.output_format
.as_ref()
.and_then(|s| OutputFormat::from_str(s).ok())
.unwrap_or(OutputFormat::Table)
}
pub fn cmd_args_meta_plugin_types(cmd: &mut Command, args: &Args) -> Vec<MetaPluginType> {
let mut meta_plugin_types = Vec::new();
@@ -200,3 +194,83 @@ pub fn cmd_args_meta_plugin_types(cmd: &mut Command, args: &Args) -> Vec<MetaPlu
meta_plugin_types
}
pub fn settings_meta_plugin_types(cmd: &mut Command, settings: &crate::config::Settings) -> Vec<MetaPluginType> {
let mut meta_plugin_types = Vec::new();
// Handle comma-separated values in each meta_plugins argument
for meta_plugin_names_str in &settings.meta_plugins {
let meta_plugin_names: Vec<&str> = meta_plugin_names_str.split(',').collect();
for name in meta_plugin_names {
let trimmed_name = name.trim();
if trimmed_name.is_empty() {
continue;
}
// Try to find the MetaPluginType by meta name
let mut found = false;
for meta_plugin_type in MetaPluginType::iter() {
let mut meta_plugin = crate::meta_plugin::get_meta_plugin(meta_plugin_type.clone());
if meta_plugin.meta_name() == trimmed_name {
meta_plugin_types.push(meta_plugin_type);
found = true;
break;
}
}
if !found {
cmd.error(
ErrorKind::InvalidValue,
format!("Unknown meta plugin type: {}", trimmed_name),
)
.exit();
}
}
}
meta_plugin_types
}
pub fn settings_digest_type(cmd: &mut Command, settings: &crate::config::Settings) -> MetaPluginType {
let digest_name = settings
.digest
.clone()
.unwrap_or(MetaPluginType::DigestSha256.to_string());
let digest_type_opt = MetaPluginType::from_str(&digest_name);
if digest_type_opt.is_err() {
cmd.error(
ErrorKind::InvalidValue,
format!("Invalid digest algorithm '{}'. Use 'sha256' or 'md5'", digest_name),
)
.exit();
}
digest_type_opt.unwrap()
}
pub fn settings_compression_type(cmd: &mut Command, settings: &crate::config::Settings) -> CompressionType {
let compression_name = settings
.compression
.clone()
.unwrap_or(CompressionType::LZ4.to_string());
let compression_type_opt = CompressionType::from_str(&compression_name);
if compression_type_opt.is_err() {
cmd.error(
ErrorKind::InvalidValue,
format!("Invalid compression algorithm '{}'. Supported algorithms: lz4, gzip, xz, zstd", compression_name),
)
.exit();
}
compression_type_opt.unwrap()
}
pub fn settings_output_format(settings: &crate::config::Settings) -> OutputFormat {
settings.output_format
.as_ref()
.and_then(|s| OutputFormat::from_str(s).ok())
.unwrap_or(OutputFormat::Table)
}

View File

@@ -10,7 +10,8 @@ use rusqlite::Connection;
pub fn mode_delete(
cmd: &mut Command,
_args: &crate::Args,
_settings: &crate::config::Settings,
_config: &crate::config::Config,
ids: &mut Vec<i64>,
tags: &mut Vec<String>,
conn: &mut Connection,

View File

@@ -293,7 +293,8 @@ fn handle_diff_output(
pub fn mode_diff(
cmd: &mut Command,
_args: &crate::Args,
_settings: &crate::config::Settings,
_config: &crate::config::Config,
ids: &mut Vec<i64>,
tags: &mut Vec<String>,
conn: &mut rusqlite::Connection,

View File

@@ -10,7 +10,8 @@ use is_terminal::IsTerminal;
pub fn mode_get(
cmd: &mut Command,
args: &crate::Args,
settings: &crate::config::Settings,
_config: &crate::config::Config,
ids: &mut Vec<i64>,
tags: &mut Vec<String>,
conn: &mut rusqlite::Connection,
@@ -23,7 +24,7 @@ pub fn mode_get(
}
let mut meta: std::collections::HashMap<String, String> = std::collections::HashMap::new();
for item in args.item.meta.iter() {
for item in settings.meta.iter() {
let item = item.clone();
meta.insert(item.key, item.value);
}
@@ -47,7 +48,7 @@ pub fn mode_get(
item_path.push(item_id.to_string());
// Determine if we should detect binary data
let mut detect_binary = !args.options.force && std::io::stdout().is_terminal();
let mut detect_binary = !settings.force && std::io::stdout().is_terminal();
// If we're detecting binary and there's binary metadata, check it
if detect_binary {

View File

@@ -19,7 +19,8 @@ use prettytable::{Attr, Cell, Row, Table};
pub fn mode_info(
cmd: &mut Command,
args: &crate::Args,
settings: &crate::config::Settings,
_config: &crate::config::Config,
ids: &mut Vec<i64>,
tags: &mut Vec<String>,
conn: &mut rusqlite::Connection,
@@ -32,7 +33,7 @@ pub fn mode_info(
}
let mut meta: std::collections::HashMap<String, String> = std::collections::HashMap::new();
for item in args.item.meta.iter() {
for item in settings.meta.iter() {
let item = item.clone();
meta.insert(item.key, item.value);
}
@@ -46,7 +47,7 @@ pub fn mode_info(
};
match item_maybe {
Some(item) => show_item(item, args, conn, data_path),
Some(item) => show_item(item, settings, conn, data_path),
None => Err(anyhow!("Unable to find matching item in database")),
}
}
@@ -67,7 +68,7 @@ struct ItemInfo {
fn show_item(
item: Item, // Using the provided struct definition
args: &crate::Args,
settings: &crate::config::Settings,
conn: &mut rusqlite::Connection,
data_path: PathBuf,
) -> anyhow::Result<()> {
@@ -78,10 +79,10 @@ fn show_item(
.map(|x| x.name)
.collect();
let output_format = get_output_format(args);
let output_format = crate::modes::common::settings_output_format(settings);
if output_format != OutputFormat::Table {
return show_item_structured(item, args, conn, data_path, output_format);
return show_item_structured(item, settings, conn, data_path, output_format);
}
let mut table = Table::new();
@@ -111,7 +112,7 @@ fn show_item(
]));
let size_cell = match item.size {
Some(size) => Cell::new(format_size(size as u64, args.options.human_readable).as_str()),
Some(size) => Cell::new(format_size(size as u64, settings.human_readable).as_str()),
None => Cell::new("Missing")
.with_style(Attr::ForegroundColor(prettytable::color::RED))
.with_style(Attr::Bold),
@@ -132,7 +133,7 @@ fn show_item(
let file_size_cell = match item_path_buf.metadata() {
Ok(metadata) => {
Cell::new(format_size(metadata.len(), args.options.human_readable).as_str())
Cell::new(format_size(metadata.len(), settings.human_readable).as_str())
}
Err(_) => Cell::new("Missing")
.with_style(Attr::ForegroundColor(prettytable::color::RED))
@@ -162,7 +163,7 @@ fn show_item(
fn show_item_structured(
item: Item,
args: &crate::Args,
settings: &crate::config::Settings,
conn: &mut rusqlite::Connection,
data_path: PathBuf,
output_format: OutputFormat,
@@ -178,12 +179,12 @@ fn show_item_structured(
let file_size = item_path_buf.metadata().map(|m| m.len()).ok();
let file_size_formatted = match file_size {
Some(size) => format_size(size, args.options.human_readable),
Some(size) => format_size(size, settings.human_readable),
None => "Missing".to_string(),
};
let stream_size_formatted = match item.size {
Some(size) => format_size(size as u64, args.options.human_readable),
Some(size) => format_size(size as u64, settings.human_readable),
None => "Missing".to_string(),
};

View File

@@ -27,7 +27,8 @@ struct ListItem {
pub fn mode_list(
cmd: &mut clap::Command,
args: &crate::Args,
settings: &crate::config::Settings,
_config: &crate::config::Config,
ids: &mut Vec<i64>,
tags: &Vec<String>,
conn: &mut rusqlite::Connection,
@@ -42,7 +43,7 @@ pub fn mode_list(
}
let mut meta: std::collections::HashMap<String, String> = std::collections::HashMap::new();
for item in args.item.meta.iter() {
for item in settings.meta.iter() {
let item = item.clone();
meta.insert(item.key, item.value);
}
@@ -71,16 +72,16 @@ pub fn mode_list(
// Fetch all metadata for all items in a single query
let meta_by_item = crate::db::get_meta_for_items(conn, &item_ids)?;
let output_format = get_output_format(args);
let output_format = crate::modes::common::settings_output_format(settings);
if output_format != OutputFormat::Table {
return show_list_structured(items, tags_by_item, meta_by_item, data_path, args, output_format);
return show_list_structured(items, tags_by_item, meta_by_item, data_path, settings, output_format);
}
let mut table = Table::new();
table.set_format(*prettytable::format::consts::FORMAT_CLEAN);
let list_format = args.options.list_format.split(",");
let list_format = settings.list_format.split(",");
let mut title_row = row!();
@@ -141,7 +142,7 @@ pub fn mode_list(
)),
ColumnType::Size => match item.size {
Some(size) => Cell::new_align(
&size_column(size as u64, args.options.human_readable, column_width),
&size_column(size as u64, settings.human_readable, column_width),
Alignment::RIGHT,
),
None => match item_path.metadata() {
@@ -158,7 +159,7 @@ pub fn mode_list(
},
ColumnType::FileSize => match item_path.metadata() {
Ok(metadata) => Cell::new_align(
&size_column(metadata.len(), args.options.human_readable, column_width),
&size_column(metadata.len(), settings.human_readable, column_width),
Alignment::RIGHT,
),
Err(_) => Cell::new_align("Missing", Alignment::RIGHT)
@@ -195,7 +196,7 @@ fn show_list_structured(
tags_by_item: std::collections::HashMap<i64, Vec<String>>,
meta_by_item: std::collections::HashMap<i64, std::collections::HashMap<String, String>>,
data_path: std::path::PathBuf,
args: &crate::Args,
settings: &crate::config::Settings,
output_format: OutputFormat,
) -> anyhow::Result<()> {
let mut list_items = Vec::new();
@@ -210,12 +211,12 @@ fn show_list_structured(
let file_size = item_path.metadata().map(|m| m.len()).ok();
let file_size_formatted = match file_size {
Some(size) => crate::modes::common::format_size(size, args.options.human_readable),
Some(size) => crate::modes::common::format_size(size, settings.human_readable),
None => "Missing".to_string(),
};
let size_formatted = match item.size {
Some(size) => crate::modes::common::format_size(size as u64, args.options.human_readable),
Some(size) => crate::modes::common::format_size(size as u64, settings.human_readable),
None => "Unknown".to_string(),
};

View File

@@ -4,7 +4,7 @@ use log::debug;
use std::io::{Read, Write, IsTerminal};
// Import the missing functions from common module
use crate::modes::common::{cmd_args_digest_type, cmd_args_compression_type, cmd_args_meta_plugin_types};
use crate::modes::common::{settings_digest_type, settings_compression_type, settings_meta_plugin_types};
fn validate_save_args(cmd: &mut Command, ids: &Vec<i64>) {
if !ids.is_empty() {
@@ -24,18 +24,18 @@ fn initialize_tags(tags: &mut Vec<String>) {
fn setup_compression_and_plugins(
cmd: &mut Command,
args: &crate::Args,
settings: &crate::config::Settings,
) -> (crate::compression_engine::CompressionType, Box<dyn crate::compression_engine::CompressionEngine>, Vec<Box<dyn crate::meta_plugin::MetaPlugin>>) {
let digest_type = cmd_args_digest_type(cmd, &args);
let digest_type = settings_digest_type(cmd, settings);
debug!("MAIN: Digest type: {:?}", digest_type);
let compression_type = cmd_args_compression_type(cmd, &args);
let compression_type = settings_compression_type(cmd, settings);
debug!("MAIN: Compression type: {:?}", compression_type);
let compression_engine =
crate::compression_engine::get_compression_engine(compression_type.clone()).expect("Unable to get compression engine");
// Start with meta plugin types from command line
let mut meta_plugin_types: Vec<crate::meta_plugin::MetaPluginType> = cmd_args_meta_plugin_types(cmd, &args);
// Start with meta plugin types from settings
let mut meta_plugin_types: Vec<crate::meta_plugin::MetaPluginType> = settings_meta_plugin_types(cmd, settings);
debug!("MAIN: Meta plugin types: {:?}", meta_plugin_types);
// Convert digest type to meta plugin type and add to the list if needed
@@ -78,7 +78,7 @@ fn setup_compression_and_plugins(
fn create_and_log_item(
conn: &mut rusqlite::Connection,
args: &crate::Args,
settings: &crate::config::Settings,
tags: &Vec<String>,
compression_type: &crate::compression_engine::CompressionType,
) -> Result<crate::db::Item, anyhow::Error> {
@@ -93,7 +93,7 @@ fn create_and_log_item(
item.id = Some(id);
debug!("MAIN: Added item {:?}", item.clone());
if !args.options.quiet {
if !settings.quiet {
if std::io::stderr().is_terminal() {
let mut t = term::stderr().unwrap();
t.reset().unwrap_or(());
@@ -121,7 +121,7 @@ fn create_and_log_item(
fn setup_item_metadata(
conn: &mut rusqlite::Connection,
_args: &crate::Args,
_settings: &crate::config::Settings,
item: &crate::db::Item,
tags: &Vec<String>,
) -> Result<(), anyhow::Error> {
@@ -129,7 +129,7 @@ fn setup_item_metadata(
Ok(())
}
fn collect_item_meta(args: &crate::Args) -> std::collections::HashMap<String, String> {
fn collect_item_meta(settings: &crate::config::Settings) -> std::collections::HashMap<String, String> {
let mut item_meta: std::collections::HashMap<String, String> = crate::modes::common::get_meta_from_env();
if let Ok(hostname) = gethostname::gethostname().into_string() {
@@ -138,7 +138,7 @@ fn collect_item_meta(args: &crate::Args) -> std::collections::HashMap<String, St
}
}
for item in args.item.meta.iter() {
for item in settings.meta.iter() {
let item = item.clone();
item_meta.insert(item.key, item.value);
}
@@ -230,7 +230,8 @@ fn finalize_meta_plugins(
pub fn mode_save(
cmd: &mut Command,
args: &crate::Args,
settings: &crate::config::Settings,
_config: &crate::config::Config,
ids: &mut Vec<i64>,
tags: &mut Vec<String>,
conn: &mut rusqlite::Connection,
@@ -239,14 +240,14 @@ pub fn mode_save(
validate_save_args(cmd, ids);
initialize_tags(tags);
let (compression_type, compression_engine, mut meta_plugins) = setup_compression_and_plugins(cmd, args);
let (compression_type, compression_engine, mut meta_plugins) = setup_compression_and_plugins(cmd, settings);
let mut item = create_and_log_item(conn, args, tags, &compression_type)?;
setup_item_metadata(conn, args, &item, tags)?; // Pass mutable reference
let mut item = create_and_log_item(conn, settings, tags, &compression_type)?;
setup_item_metadata(conn, settings, &item, tags)?; // Pass mutable reference
// Save as much as possible in case something breaks - don't use transactions
// This allows partial saves to succeed even if some metadata operations fail
let item_meta = collect_item_meta(args);
let item_meta = collect_item_meta(settings);
let item_id = item.id.ok_or_else(|| anyhow!("Item missing ID"))?;
for kv in item_meta.iter() {

View File

@@ -20,26 +20,28 @@ pub use common::{ServerConfig, AppState, logging_middleware, create_auth_middlew
pub fn mode_server(
_cmd: &mut Command,
args: &crate::Args,
settings: &crate::config::Settings,
config: &crate::config::Config,
conn: &mut rusqlite::Connection,
data_path: PathBuf,
) -> Result<()> {
let server_address = args.mode.server.as_ref().unwrap();
let server_address = settings.get_server_address(&crate::args::Args::parse(), config)
.unwrap_or_else(|| "127.0.0.1:8080".to_string());
let config = ServerConfig {
address: server_address.clone(),
password: args.options.server_password.clone(),
let server_config = common::ServerConfig {
address: server_address,
password: settings.server_password.clone(),
};
// We need to move the connection into the async runtime
let rt = tokio::runtime::Runtime::new()?;
// Take ownership of the connection and move it into the async runtime
let owned_conn = std::mem::replace(conn, rusqlite::Connection::open_in_memory()?);
rt.block_on(run_server(config, owned_conn, data_path))
rt.block_on(run_server(server_config, owned_conn, data_path))
}
async fn run_server(
config: ServerConfig,
config: common::ServerConfig,
conn: rusqlite::Connection,
data_dir: PathBuf,
) -> Result<()> {

View File

@@ -119,15 +119,16 @@ fn build_meta_plugin_table(meta_plugin_info: &Vec<MetaPluginInfo>) -> Table {
pub fn mode_status(
_cmd: &mut Command,
args: &crate::Args,
settings: &crate::config::Settings,
_config: &crate::config::Config,
data_path: PathBuf,
db_path: PathBuf,
) -> Result<(), anyhow::Error> {
// Determine which meta plugins would be enabled for a save operation
let mut meta_plugin_types: Vec<MetaPluginType> = crate::modes::common::cmd_args_meta_plugin_types(_cmd, &args);
let mut meta_plugin_types: Vec<MetaPluginType> = crate::modes::common::settings_meta_plugin_types(_cmd, settings);
// Add digest type if specified
let digest_type = crate::modes::common::cmd_args_digest_type(_cmd, &args);
let digest_type = crate::modes::common::settings_digest_type(_cmd, settings);
let digest_meta_plugin_type = match digest_type {
crate::meta_plugin::MetaPluginType::DigestSha256 => Some(MetaPluginType::DigestSha256),
crate::meta_plugin::MetaPluginType::DigestMd5 => Some(MetaPluginType::DigestMd5),
@@ -140,7 +141,7 @@ pub fn mode_status(
}
}
let output_format = get_output_format(args);
let output_format = crate::modes::common::settings_output_format(settings);
let status_info = generate_status_info(data_path, db_path, &meta_plugin_types);
match output_format {

View File

@@ -13,7 +13,8 @@ use rusqlite::Connection;
pub fn mode_update(
cmd: &mut Command,
args: &crate::Args,
settings: &crate::config::Settings,
_config: &crate::config::Config,
ids: &mut Vec<i64>,
tags: &mut Vec<String>,
conn: &mut Connection,
@@ -71,7 +72,7 @@ pub fn mode_update(
}
}
let digest_type = cmd_args_digest_type(cmd, args);
let digest_type = crate::modes::common::settings_digest_type(cmd, settings);
let digest_meta = get_digest_type_meta(digest_type.clone());
let digest_value = db::get_item_meta_value(&tx, &item, digest_meta)?;
@@ -115,9 +116,9 @@ pub fn mode_update(
}
}
if !args.item.meta.is_empty() {
if !settings.meta.is_empty() {
debug!("MAIN: Updating item meta");
for kv in args.item.meta.iter() {
for kv in settings.meta.iter() {
let meta = db::Meta {
id: item_id,
name: kv.key.to_string(),