
- Complete removal of polling loops for better performance - Event-driven file processing with stability tracking - Proper structured logging with tracing - Graceful shutdown handling (SIGTERM/SIGINT) - Disk space monitoring (5GB minimum threshold) - Production-ready paths for RTSDA deployment - Shared video_processing crate integration - Filename compatibility improvements (| → () separator) - Environment-based configuration - Comprehensive error handling and logging Performance improvements: - No more wasteful 1-2 second polling - Only processes files when filesystem events occur - Proper resource management and cleanup Production features: - Default paths: /home/rockvilleav/.rtsda/livestreams → /media/archive/jellyfin/livestreams - Configurable via INPUT_DIR and OUTPUT_DIR environment variables - Structured logging configurable via RUST_LOG - Hardware-accelerated video conversion (Intel QSV) - NFO file generation for Jellyfin compatibility
172 lines
6.3 KiB
Rust
172 lines
6.3 KiB
Rust
use std::env;
|
|
use std::fs;
|
|
use std::path::PathBuf;
|
|
use tempfile::TempDir;
|
|
use chrono::{NaiveDateTime, Datelike, Timelike};
|
|
use livestream_archiver::LivestreamArchiver;
|
|
|
|
#[tokio::test]
|
|
async fn test_livestream_archiver_creation() {
|
|
let temp_output = TempDir::new().unwrap();
|
|
|
|
// Test that we can create a new archiver
|
|
let archiver = LivestreamArchiver::new(temp_output.path().to_path_buf());
|
|
|
|
assert_eq!(archiver.get_output_path(), temp_output.path());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_date_extraction_from_filename() {
|
|
let temp_output = TempDir::new().unwrap();
|
|
let archiver = LivestreamArchiver::new(temp_output.path().to_path_buf());
|
|
|
|
// Test valid filename
|
|
let result = archiver.extract_date_from_filename("2024-12-25_14-30-45.mp4").await;
|
|
assert!(result.is_ok());
|
|
|
|
let date = result.unwrap();
|
|
assert_eq!(date.year(), 2024);
|
|
assert_eq!(date.month(), 12);
|
|
assert_eq!(date.day(), 25);
|
|
assert_eq!(date.hour(), 14);
|
|
assert_eq!(date.minute(), 30);
|
|
assert_eq!(date.second(), 45);
|
|
|
|
// Test invalid filename
|
|
let result = archiver.extract_date_from_filename("invalid-filename.mp4").await;
|
|
assert!(result.is_err());
|
|
|
|
// Test non-mp4 file
|
|
let result = archiver.extract_date_from_filename("2024-12-25_14-30-45.avi").await;
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_file_already_processed_detection() {
|
|
let temp_output = TempDir::new().unwrap();
|
|
|
|
// Set up environment variables for program names
|
|
env::set_var("DIVINE_WORSHIP_NAME", "Test Divine Worship");
|
|
env::set_var("AFTERNOON_PROGRAM_NAME", "Test Afternoon Program");
|
|
|
|
let archiver = LivestreamArchiver::new(temp_output.path().to_path_buf());
|
|
|
|
let test_date = NaiveDateTime::parse_from_str("2024-12-25_14-30-45", "%Y-%m-%d_%H-%M-%S").unwrap();
|
|
|
|
// Initially no files should exist
|
|
assert!(!archiver.check_if_file_already_processed("2024-12-25_14-30-45.mp4", &test_date));
|
|
|
|
// Create the expected output directory structure
|
|
let year_dir = temp_output.path().join("2024");
|
|
let month_dir = year_dir.join("12-December");
|
|
fs::create_dir_all(&month_dir).unwrap();
|
|
|
|
// Create a divine worship file
|
|
let divine_worship_file = month_dir.join("Test Divine Worship (December 25 2024).mp4");
|
|
fs::write(&divine_worship_file, "test content").unwrap();
|
|
|
|
// Now it should detect the file as processed
|
|
assert!(archiver.check_if_file_already_processed("2024-12-25_14-30-45.mp4", &test_date));
|
|
|
|
// Clean up environment variables
|
|
env::remove_var("DIVINE_WORSHIP_NAME");
|
|
env::remove_var("AFTERNOON_PROGRAM_NAME");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_environment_variable_configuration() {
|
|
// Test with custom environment variables
|
|
env::set_var("DIVINE_WORSHIP_NAME", "Custom Divine Service");
|
|
env::set_var("AFTERNOON_PROGRAM_NAME", "Custom Afternoon Show");
|
|
env::set_var("SHOW_TITLE", "Custom Livestreams");
|
|
|
|
let temp_output = TempDir::new().unwrap();
|
|
let archiver = LivestreamArchiver::new(temp_output.path().to_path_buf());
|
|
|
|
// These are internal fields, so we test them indirectly through the file processing logic
|
|
let test_date = NaiveDateTime::parse_from_str("2024-12-25_14-30-45", "%Y-%m-%d_%H-%M-%S").unwrap();
|
|
|
|
// The archiver should use the custom names when checking for existing files
|
|
assert!(!archiver.check_if_file_already_processed("test.mp4", &test_date));
|
|
|
|
// Create expected directory structure
|
|
let year_dir = temp_output.path().join("2024");
|
|
let month_dir = year_dir.join("12-December");
|
|
fs::create_dir_all(&month_dir).unwrap();
|
|
|
|
// Create file with custom name
|
|
let custom_file = month_dir.join("Custom Divine Service (December 25 2024).mp4");
|
|
fs::write(&custom_file, "test content").unwrap();
|
|
|
|
// Should now detect as processed
|
|
assert!(archiver.check_if_file_already_processed("test.mp4", &test_date));
|
|
|
|
// Clean up
|
|
env::remove_var("DIVINE_WORSHIP_NAME");
|
|
env::remove_var("AFTERNOON_PROGRAM_NAME");
|
|
env::remove_var("SHOW_TITLE");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_stability_tracking_integration() {
|
|
let temp_output = TempDir::new().unwrap();
|
|
let archiver = LivestreamArchiver::new(temp_output.path().to_path_buf());
|
|
|
|
let temp_input = TempDir::new().unwrap();
|
|
let test_file = temp_input.path().join("2024-12-25_14-30-45.mp4");
|
|
|
|
// Create initial file
|
|
fs::write(&test_file, "initial content").unwrap();
|
|
|
|
// Test that stability tracking starts
|
|
let result = archiver.on_file_event(&test_file);
|
|
assert!(result.is_ok());
|
|
assert!(!result.unwrap(), "New file should not be immediately stable");
|
|
|
|
// File should be tracked
|
|
let tracked_files = archiver.get_tracked_files();
|
|
assert_eq!(tracked_files.len(), 1);
|
|
assert_eq!(tracked_files[0], test_file);
|
|
|
|
// Remove tracking
|
|
archiver.remove_file_tracker(&test_file);
|
|
assert_eq!(archiver.get_tracked_files().len(), 0);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_directory_structure_creation() {
|
|
let temp_output = TempDir::new().unwrap();
|
|
|
|
// Test date parsing and directory structure
|
|
let test_cases = vec![
|
|
("2024-01-15_10-30-00.mp4", "2024", "01-January"),
|
|
("2024-12-31_23-59-59.mp4", "2024", "12-December"),
|
|
("2023-07-04_12-00-00.mp4", "2023", "07-July"),
|
|
];
|
|
|
|
for (filename, expected_year, expected_month) in test_cases {
|
|
let archiver = LivestreamArchiver::new(temp_output.path().to_path_buf());
|
|
|
|
let date = archiver.extract_date_from_filename(filename).await.unwrap();
|
|
|
|
// The directory structure should match what the archiver expects
|
|
let year_dir = temp_output.path().join(expected_year);
|
|
let month_dir = year_dir.join(expected_month);
|
|
|
|
// Create the directory structure as the archiver would
|
|
fs::create_dir_all(&month_dir).unwrap();
|
|
|
|
// Verify structure exists
|
|
assert!(year_dir.exists());
|
|
assert!(month_dir.exists());
|
|
|
|
// Test file detection logic
|
|
assert!(!archiver.check_if_file_already_processed(filename, &date));
|
|
}
|
|
}
|
|
|
|
// Helper function to create mock video files for testing
|
|
fn create_mock_mp4_file(path: &PathBuf, size_mb: usize) -> std::io::Result<()> {
|
|
let content = vec![0u8; size_mb * 1024 * 1024]; // Create file of specified size in MB
|
|
fs::write(path, content)
|
|
} |