- 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
165 lines
4.4 KiB
Rust
165 lines
4.4 KiB
Rust
use anyhow::{Context, Result, anyhow};
|
|
use log::*;
|
|
use std::fs::File;
|
|
use std::io::{Read, Write};
|
|
use std::path::PathBuf;
|
|
use std::process::{Child, Command, Stdio};
|
|
use which::which;
|
|
|
|
use crate::compression_engine::CompressionEngine;
|
|
|
|
pub struct ProgramReader {
|
|
process: Child,
|
|
stdout: Option<std::process::ChildStdout>,
|
|
}
|
|
|
|
impl Read for ProgramReader {
|
|
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
|
self.stdout.as_mut().unwrap().read(buf)
|
|
}
|
|
}
|
|
|
|
impl Drop for ProgramReader {
|
|
fn drop(&mut self) {
|
|
// Ensure the process is waited on to prevent zombie processes
|
|
let _ = self.process.wait();
|
|
}
|
|
}
|
|
|
|
pub struct ProgramWriter {
|
|
process: Child,
|
|
stdin: Option<std::process::ChildStdin>,
|
|
}
|
|
|
|
impl Write for ProgramWriter {
|
|
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
|
self.stdin.as_mut().unwrap().write(buf)
|
|
}
|
|
|
|
fn flush(&mut self) -> std::io::Result<()> {
|
|
self.stdin.as_mut().unwrap().flush()
|
|
}
|
|
}
|
|
|
|
impl Drop for ProgramWriter {
|
|
fn drop(&mut self) {
|
|
// Close stdin to signal EOF to the child process
|
|
drop(self.stdin.take());
|
|
// Ensure the process is waited on to prevent zombie processes
|
|
let _ = self.process.wait();
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Eq, PartialEq, Clone)]
|
|
pub struct CompressionEngineProgram {
|
|
pub program: String,
|
|
pub compress: Vec<String>,
|
|
pub decompress: Vec<String>,
|
|
pub supported: bool,
|
|
}
|
|
|
|
impl CompressionEngineProgram {
|
|
pub fn new(
|
|
program: &str,
|
|
compress: Vec<&str>,
|
|
decompress: Vec<&str>,
|
|
) -> CompressionEngineProgram {
|
|
let program_path = which(program);
|
|
let supported = program_path.is_ok();
|
|
|
|
CompressionEngineProgram {
|
|
program: program_path
|
|
.map_or_else(|_| program.to_string(), |p| p.to_string_lossy().to_string()),
|
|
compress: compress.iter().map(|s| s.to_string()).collect(),
|
|
decompress: decompress.iter().map(|s| s.to_string()).collect(),
|
|
supported,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl CompressionEngine for CompressionEngineProgram {
|
|
fn is_supported(&self) -> bool {
|
|
self.supported
|
|
}
|
|
|
|
fn is_internal(&self) -> bool {
|
|
false
|
|
}
|
|
|
|
fn get_status_info(&self) -> (String, String, String) {
|
|
(
|
|
self.program.clone(),
|
|
self.compress.join(" "),
|
|
self.decompress.join(" "),
|
|
)
|
|
}
|
|
|
|
fn open(&self, file_path: PathBuf) -> Result<Box<dyn Read + Send>> {
|
|
debug!("COMPRESSION: Opening {file_path:?} using {self:?}");
|
|
|
|
let program = self.program.clone();
|
|
let args = self.decompress.clone();
|
|
|
|
debug!("COMPRESSION: Executing command: {program:?} {args:?} reading from {file_path:?}");
|
|
|
|
let file = File::open(file_path).context("Unable to open file for reading")?;
|
|
|
|
let mut process = Command::new(program.clone())
|
|
.args(args.clone())
|
|
.stdin(file)
|
|
.stdout(Stdio::piped())
|
|
.spawn()
|
|
.context(anyhow!(
|
|
"Unable to spawn child process: {:?} {:?}",
|
|
program,
|
|
args
|
|
))?;
|
|
|
|
let stdout = process
|
|
.stdout
|
|
.take()
|
|
.ok_or_else(|| anyhow!("Failed to capture stdout from child process"))?;
|
|
|
|
Ok(Box::new(ProgramReader {
|
|
process,
|
|
stdout: Some(stdout),
|
|
}))
|
|
}
|
|
|
|
fn create(&self, file_path: PathBuf) -> Result<Box<dyn Write>> {
|
|
debug!("COMPRESSION: Writing to {file_path:?} using {self:?}");
|
|
|
|
let program = self.program.clone();
|
|
let args = self.compress.clone();
|
|
|
|
debug!("COMPRESSION: Executing command: {program:?} {args:?} writing to {file_path:?}");
|
|
|
|
let file = File::create(file_path).context("Unable to open file for writing")?;
|
|
|
|
let mut process = Command::new(program.clone())
|
|
.args(args.clone())
|
|
.stdin(Stdio::piped())
|
|
.stdout(file)
|
|
.spawn()
|
|
.context(anyhow!(
|
|
"Problem spawning child process: {:?} {:?}",
|
|
program,
|
|
args
|
|
))?;
|
|
|
|
let stdin = process
|
|
.stdin
|
|
.take()
|
|
.ok_or_else(|| anyhow!("Failed to capture stdin from child process"))?;
|
|
|
|
Ok(Box::new(ProgramWriter {
|
|
process,
|
|
stdin: Some(stdin),
|
|
}))
|
|
}
|
|
|
|
fn clone_box(&self) -> Box<dyn CompressionEngine> {
|
|
Box::new(self.clone())
|
|
}
|
|
}
|