104 lines
3.6 KiB
Rust
104 lines
3.6 KiB
Rust
use anyhow::{anyhow, Result};
|
|
use std::io::Write;
|
|
|
|
use crate::common::is_binary::is_binary;
|
|
use crate::common::PIPESIZE;
|
|
use crate::config;
|
|
use crate::filter_plugin::FilterChain;
|
|
use crate::services::item_service::ItemService;
|
|
use clap::Command;
|
|
use is_terminal::IsTerminal;
|
|
use std::path::PathBuf;
|
|
use std::io::Read;
|
|
|
|
/// Handles the get mode: retrieves and streams item content to stdout, applying filters if specified.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `cmd` - Clap command for error handling.
|
|
/// * `settings` - Global settings, including force output flag.
|
|
/// * `ids` - List of item IDs (at most one).
|
|
/// * `tags` - List of tags to match (mutually exclusive with IDs).
|
|
/// * `conn` - Database connection.
|
|
/// * `data_path` - Path to data directory.
|
|
/// * `filter_chain` - Optional pre-parsed filter chain to apply to content.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// `Result<()>` on success, or an error if item not found or output fails.
|
|
pub fn mode_get(
|
|
cmd: &mut Command,
|
|
settings: &config::Settings,
|
|
ids: &mut Vec<i64>,
|
|
tags: &mut Vec<String>,
|
|
conn: &mut rusqlite::Connection,
|
|
data_path: PathBuf,
|
|
filter_chain: Option<FilterChain>,
|
|
) -> Result<()> {
|
|
if !ids.is_empty() && !tags.is_empty() {
|
|
cmd.error(clap::error::ErrorKind::InvalidValue, "Both ID and tags given, you must supply either IDs or tags when using --get").exit();
|
|
} else if ids.len() > 1 {
|
|
cmd.error(clap::error::ErrorKind::InvalidValue, "More than one ID given, you must supply exactly one ID when using --get").exit();
|
|
}
|
|
// If both are empty, find_item will find the last item
|
|
|
|
let item_service = ItemService::new(data_path.clone());
|
|
let item_with_meta = item_service.find_item(conn, ids, tags, &std::collections::HashMap::new())
|
|
.map_err(|e| anyhow!("Unable to find matching item in database: {}", e))?;
|
|
|
|
let item_id = item_with_meta.item.id.unwrap();
|
|
|
|
// Determine if we should detect binary data
|
|
let mut detect_binary = !settings.force && std::io::stdout().is_terminal();
|
|
|
|
if detect_binary {
|
|
let meta_map = item_with_meta.meta_as_map();
|
|
if let Some(text_val) = meta_map.get("text") {
|
|
if text_val == "true" {
|
|
detect_binary = false;
|
|
} else if text_val == "false" {
|
|
return Err(anyhow!(
|
|
"Refusing to output binary data to TTY, use --force to override"
|
|
));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get a reader that applies the filters using the pre-parsed filter chain
|
|
let (mut reader, _, _) = item_service.get_item_content_info_streaming_with_chain(
|
|
conn,
|
|
item_id,
|
|
filter_chain.as_ref(),
|
|
)?;
|
|
|
|
if detect_binary {
|
|
// Read only the first 8192 bytes for binary detection
|
|
let mut sample_buffer = vec![0; PIPESIZE];
|
|
let bytes_read = reader.read(&mut sample_buffer)?;
|
|
if is_binary(&sample_buffer[..bytes_read]) {
|
|
return Err(anyhow!(
|
|
"Refusing to output binary data to TTY, use --force to override"
|
|
));
|
|
}
|
|
// We need to create a new reader since we consumed some bytes
|
|
let (new_reader, _, _) = item_service.get_item_content_info_streaming_with_chain(
|
|
conn,
|
|
item_id,
|
|
filter_chain.as_ref(),
|
|
)?;
|
|
reader = new_reader;
|
|
}
|
|
|
|
// Stream the content to stdout
|
|
let mut stdout = std::io::stdout();
|
|
let mut buffer = [0; PIPESIZE];
|
|
loop {
|
|
let bytes_read = reader.read(&mut buffer)?;
|
|
if bytes_read == 0 {
|
|
break;
|
|
}
|
|
stdout.write_all(&buffer[..bytes_read])?;
|
|
}
|
|
Ok(())
|
|
}
|