generated from nhcarrigan/template
ea111569af
Removes temporary debugging docs and excessive logging that were added to diagnose and fix permission modal issues. Cleaned up: - Deleted DEBUGGING.md (temporary troubleshooting guide) - Deleted FIXES-2026-02-06.md (temporary fix summary) - Removed debug logging from all Rust modules
427 lines
14 KiB
Rust
427 lines
14 KiB
Rust
use parking_lot::Mutex;
|
|
use std::collections::HashMap;
|
|
use std::fs;
|
|
use std::path::{Path, PathBuf};
|
|
use std::sync::Arc;
|
|
use uuid::Uuid;
|
|
|
|
const TEMP_DIR_NAME: &str = "hikari-uploads";
|
|
|
|
pub struct TempFileManager {
|
|
base_dir: PathBuf,
|
|
files: HashMap<String, Vec<PathBuf>>,
|
|
}
|
|
|
|
impl TempFileManager {
|
|
pub fn new() -> Result<Self, String> {
|
|
let base_dir = std::env::temp_dir().join(TEMP_DIR_NAME);
|
|
|
|
if !base_dir.exists() {
|
|
fs::create_dir_all(&base_dir)
|
|
.map_err(|e| format!("Failed to create temp directory: {}", e))?;
|
|
}
|
|
|
|
Ok(TempFileManager {
|
|
base_dir,
|
|
files: HashMap::new(),
|
|
})
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
pub fn get_base_dir(&self) -> &Path {
|
|
&self.base_dir
|
|
}
|
|
|
|
pub fn save_file(
|
|
&mut self,
|
|
conversation_id: &str,
|
|
data: &[u8],
|
|
original_filename: Option<&str>,
|
|
) -> Result<PathBuf, String> {
|
|
let unique_id = Uuid::new_v4();
|
|
let extension = original_filename
|
|
.and_then(|name| Path::new(name).extension())
|
|
.and_then(|ext| ext.to_str())
|
|
.unwrap_or("bin");
|
|
|
|
let filename = format!("{}_{}.{}", conversation_id, unique_id, extension);
|
|
let file_path = self.base_dir.join(&filename);
|
|
|
|
fs::write(&file_path, data)
|
|
.map_err(|e| format!("Failed to write temp file: {}", e))?;
|
|
|
|
self.files
|
|
.entry(conversation_id.to_string())
|
|
.or_default()
|
|
.push(file_path.clone());
|
|
|
|
Ok(file_path)
|
|
}
|
|
|
|
pub fn register_file(&mut self, conversation_id: &str, file_path: PathBuf) {
|
|
self.files
|
|
.entry(conversation_id.to_string())
|
|
.or_default()
|
|
.push(file_path);
|
|
}
|
|
|
|
pub fn get_files_for_conversation(&self, conversation_id: &str) -> Vec<PathBuf> {
|
|
self.files
|
|
.get(conversation_id)
|
|
.cloned()
|
|
.unwrap_or_default()
|
|
}
|
|
|
|
pub fn cleanup_conversation(&mut self, conversation_id: &str) -> Result<(), String> {
|
|
if let Some(files) = self.files.remove(conversation_id) {
|
|
for file_path in files {
|
|
if file_path.exists() {
|
|
if let Err(e) = fs::remove_file(&file_path) {
|
|
tracing::warn!(
|
|
"Failed to remove temp file {:?}: {}",
|
|
file_path, e
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn cleanup_all(&mut self) -> Result<(), String> {
|
|
let conversation_ids: Vec<String> = self.files.keys().cloned().collect();
|
|
|
|
for conversation_id in conversation_ids {
|
|
self.cleanup_conversation(&conversation_id)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn cleanup_orphaned_files(&mut self) -> Result<usize, String> {
|
|
let mut cleaned_count = 0;
|
|
|
|
if !self.base_dir.exists() {
|
|
return Ok(0);
|
|
}
|
|
|
|
let tracked_files: std::collections::HashSet<PathBuf> =
|
|
self.files.values().flatten().cloned().collect();
|
|
|
|
let entries = fs::read_dir(&self.base_dir)
|
|
.map_err(|e| format!("Failed to read temp directory: {}", e))?;
|
|
|
|
for entry in entries.flatten() {
|
|
let path = entry.path();
|
|
if path.is_file() && !tracked_files.contains(&path) {
|
|
if let Err(e) = fs::remove_file(&path) {
|
|
tracing::warn!("Failed to remove orphaned file {:?}: {}", path, e);
|
|
} else {
|
|
cleaned_count += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(cleaned_count)
|
|
}
|
|
}
|
|
|
|
impl Default for TempFileManager {
|
|
fn default() -> Self {
|
|
Self::new().expect("Failed to create TempFileManager")
|
|
}
|
|
}
|
|
|
|
pub type SharedTempFileManager = Arc<Mutex<TempFileManager>>;
|
|
|
|
pub fn create_shared_temp_manager() -> Result<SharedTempFileManager, String> {
|
|
Ok(Arc::new(Mutex::new(TempFileManager::new()?)))
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use std::fs;
|
|
use tempfile::TempDir;
|
|
|
|
// Helper to create a TempFileManager with a custom base directory for testing
|
|
fn create_test_manager(base_dir: PathBuf) -> TempFileManager {
|
|
if !base_dir.exists() {
|
|
fs::create_dir_all(&base_dir).expect("Failed to create test temp dir");
|
|
}
|
|
TempFileManager {
|
|
base_dir,
|
|
files: HashMap::new(),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_new_creates_base_directory() {
|
|
let manager = TempFileManager::new().expect("Failed to create TempFileManager");
|
|
assert!(manager.base_dir.exists());
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_base_dir_returns_correct_path() {
|
|
let temp_dir = TempDir::new().expect("Failed to create temp dir");
|
|
let base_path = temp_dir.path().join("hikari-test");
|
|
let manager = create_test_manager(base_path.clone());
|
|
|
|
assert_eq!(manager.get_base_dir(), base_path.as_path());
|
|
}
|
|
|
|
#[test]
|
|
fn test_save_file_creates_file_with_content() {
|
|
let temp_dir = TempDir::new().expect("Failed to create temp dir");
|
|
let base_path = temp_dir.path().join("hikari-test");
|
|
let mut manager = create_test_manager(base_path);
|
|
|
|
let data = b"Hello, world!";
|
|
let result = manager.save_file("conv-1", data, Some("test.txt"));
|
|
|
|
assert!(result.is_ok());
|
|
let file_path = result.unwrap();
|
|
assert!(file_path.exists());
|
|
|
|
let content = fs::read(&file_path).expect("Failed to read file");
|
|
assert_eq!(content, data);
|
|
}
|
|
|
|
#[test]
|
|
fn test_save_file_uses_correct_extension() {
|
|
let temp_dir = TempDir::new().expect("Failed to create temp dir");
|
|
let base_path = temp_dir.path().join("hikari-test");
|
|
let mut manager = create_test_manager(base_path);
|
|
|
|
let data = b"test data";
|
|
let result = manager.save_file("conv-1", data, Some("document.pdf"));
|
|
|
|
assert!(result.is_ok());
|
|
let file_path = result.unwrap();
|
|
assert_eq!(file_path.extension().unwrap(), "pdf");
|
|
}
|
|
|
|
#[test]
|
|
fn test_save_file_uses_bin_extension_when_no_filename() {
|
|
let temp_dir = TempDir::new().expect("Failed to create temp dir");
|
|
let base_path = temp_dir.path().join("hikari-test");
|
|
let mut manager = create_test_manager(base_path);
|
|
|
|
let data = b"binary data";
|
|
let result = manager.save_file("conv-1", data, None);
|
|
|
|
assert!(result.is_ok());
|
|
let file_path = result.unwrap();
|
|
assert_eq!(file_path.extension().unwrap(), "bin");
|
|
}
|
|
|
|
#[test]
|
|
fn test_register_file_tracks_file_path() {
|
|
let temp_dir = TempDir::new().expect("Failed to create temp dir");
|
|
let base_path = temp_dir.path().join("hikari-test");
|
|
let mut manager = create_test_manager(base_path);
|
|
|
|
let file_path = PathBuf::from("/some/path/file.txt");
|
|
manager.register_file("conv-1", file_path.clone());
|
|
|
|
let files = manager.get_files_for_conversation("conv-1");
|
|
assert_eq!(files.len(), 1);
|
|
assert_eq!(files[0], file_path);
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_files_for_conversation_returns_empty_for_unknown() {
|
|
let temp_dir = TempDir::new().expect("Failed to create temp dir");
|
|
let base_path = temp_dir.path().join("hikari-test");
|
|
let manager = create_test_manager(base_path);
|
|
|
|
let files = manager.get_files_for_conversation("unknown-conv");
|
|
assert!(files.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_files_for_conversation_returns_all_files() {
|
|
let temp_dir = TempDir::new().expect("Failed to create temp dir");
|
|
let base_path = temp_dir.path().join("hikari-test");
|
|
let mut manager = create_test_manager(base_path);
|
|
|
|
let data = b"test";
|
|
manager.save_file("conv-1", data, Some("file1.txt")).unwrap();
|
|
manager.save_file("conv-1", data, Some("file2.txt")).unwrap();
|
|
manager.save_file("conv-2", data, Some("file3.txt")).unwrap();
|
|
|
|
let files_conv1 = manager.get_files_for_conversation("conv-1");
|
|
let files_conv2 = manager.get_files_for_conversation("conv-2");
|
|
|
|
assert_eq!(files_conv1.len(), 2);
|
|
assert_eq!(files_conv2.len(), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn test_cleanup_conversation_removes_files() {
|
|
let temp_dir = TempDir::new().expect("Failed to create temp dir");
|
|
let base_path = temp_dir.path().join("hikari-test");
|
|
let mut manager = create_test_manager(base_path);
|
|
|
|
let data = b"test";
|
|
let file_path = manager.save_file("conv-1", data, Some("test.txt")).unwrap();
|
|
assert!(file_path.exists());
|
|
|
|
let result = manager.cleanup_conversation("conv-1");
|
|
assert!(result.is_ok());
|
|
assert!(!file_path.exists());
|
|
assert!(manager.get_files_for_conversation("conv-1").is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn test_cleanup_conversation_handles_missing_files() {
|
|
let temp_dir = TempDir::new().expect("Failed to create temp dir");
|
|
let base_path = temp_dir.path().join("hikari-test");
|
|
let mut manager = create_test_manager(base_path);
|
|
|
|
// Register a file that doesn't exist
|
|
manager.register_file("conv-1", PathBuf::from("/nonexistent/file.txt"));
|
|
|
|
// Should not error, just skip missing files
|
|
let result = manager.cleanup_conversation("conv-1");
|
|
assert!(result.is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn test_cleanup_conversation_for_unknown_returns_ok() {
|
|
let temp_dir = TempDir::new().expect("Failed to create temp dir");
|
|
let base_path = temp_dir.path().join("hikari-test");
|
|
let mut manager = create_test_manager(base_path);
|
|
|
|
let result = manager.cleanup_conversation("unknown-conv");
|
|
assert!(result.is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn test_cleanup_all_removes_all_files() {
|
|
let temp_dir = TempDir::new().expect("Failed to create temp dir");
|
|
let base_path = temp_dir.path().join("hikari-test");
|
|
let mut manager = create_test_manager(base_path);
|
|
|
|
let data = b"test";
|
|
let file1 = manager.save_file("conv-1", data, Some("f1.txt")).unwrap();
|
|
let file2 = manager.save_file("conv-2", data, Some("f2.txt")).unwrap();
|
|
|
|
assert!(file1.exists());
|
|
assert!(file2.exists());
|
|
|
|
let result = manager.cleanup_all();
|
|
assert!(result.is_ok());
|
|
|
|
assert!(!file1.exists());
|
|
assert!(!file2.exists());
|
|
assert!(manager.files.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn test_cleanup_orphaned_files_removes_untracked() {
|
|
let temp_dir = TempDir::new().expect("Failed to create temp dir");
|
|
let base_path = temp_dir.path().join("hikari-test");
|
|
let mut manager = create_test_manager(base_path.clone());
|
|
|
|
// Create a tracked file
|
|
let data = b"tracked";
|
|
let tracked_path = manager.save_file("conv-1", data, Some("tracked.txt")).unwrap();
|
|
|
|
// Create an untracked (orphaned) file directly in the temp directory
|
|
let orphan_path = base_path.join("orphan.txt");
|
|
fs::write(&orphan_path, b"orphan").expect("Failed to create orphan file");
|
|
|
|
assert!(tracked_path.exists());
|
|
assert!(orphan_path.exists());
|
|
|
|
let result = manager.cleanup_orphaned_files();
|
|
assert!(result.is_ok());
|
|
assert_eq!(result.unwrap(), 1); // One orphan removed
|
|
|
|
assert!(tracked_path.exists()); // Tracked file still exists
|
|
assert!(!orphan_path.exists()); // Orphan removed
|
|
}
|
|
|
|
#[test]
|
|
fn test_cleanup_orphaned_returns_zero_when_none() {
|
|
let temp_dir = TempDir::new().expect("Failed to create temp dir");
|
|
let base_path = temp_dir.path().join("hikari-test");
|
|
let mut manager = create_test_manager(base_path);
|
|
|
|
let data = b"test";
|
|
manager.save_file("conv-1", data, Some("test.txt")).unwrap();
|
|
|
|
let result = manager.cleanup_orphaned_files();
|
|
assert!(result.is_ok());
|
|
assert_eq!(result.unwrap(), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_cleanup_orphaned_returns_zero_when_dir_missing() {
|
|
let mut manager = TempFileManager {
|
|
base_dir: PathBuf::from("/nonexistent/dir"),
|
|
files: HashMap::new(),
|
|
};
|
|
|
|
let result = manager.cleanup_orphaned_files();
|
|
assert!(result.is_ok());
|
|
assert_eq!(result.unwrap(), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_default_creates_manager() {
|
|
// Default should work as long as we can create temp directories
|
|
let manager = TempFileManager::default();
|
|
assert!(manager.base_dir.exists());
|
|
}
|
|
|
|
#[test]
|
|
fn test_create_shared_temp_manager() {
|
|
let result = create_shared_temp_manager();
|
|
assert!(result.is_ok());
|
|
|
|
let shared = result.unwrap();
|
|
let manager = shared.lock();
|
|
assert!(manager.base_dir.exists());
|
|
}
|
|
|
|
#[test]
|
|
fn test_multiple_files_same_conversation() {
|
|
let temp_dir = TempDir::new().expect("Failed to create temp dir");
|
|
let base_path = temp_dir.path().join("hikari-test");
|
|
let mut manager = create_test_manager(base_path);
|
|
|
|
// Save multiple files to same conversation
|
|
for i in 0..5 {
|
|
let data = format!("content {}", i);
|
|
manager
|
|
.save_file("conv-1", data.as_bytes(), Some(&format!("file{}.txt", i)))
|
|
.unwrap();
|
|
}
|
|
|
|
let files = manager.get_files_for_conversation("conv-1");
|
|
assert_eq!(files.len(), 5);
|
|
|
|
// Each file should have unique content
|
|
for (i, file_path) in files.iter().enumerate() {
|
|
let content = fs::read_to_string(file_path).expect("Failed to read");
|
|
assert_eq!(content, format!("content {}", i));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_file_paths_contain_conversation_id() {
|
|
let temp_dir = TempDir::new().expect("Failed to create temp dir");
|
|
let base_path = temp_dir.path().join("hikari-test");
|
|
let mut manager = create_test_manager(base_path);
|
|
|
|
let file_path = manager
|
|
.save_file("my-conversation-id", b"test", Some("test.txt"))
|
|
.unwrap();
|
|
|
|
let filename = file_path.file_name().unwrap().to_str().unwrap();
|
|
assert!(filename.starts_with("my-conversation-id_"));
|
|
}
|
|
}
|