fix: scan status=unregistered not shown as registered; feat: config API for auto-pipeline/watcher-auto-register
This commit is contained in:
@@ -1,8 +1,11 @@
|
||||
use anyhow::Result;
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use tokio::time;
|
||||
use tracing::{info, warn};
|
||||
|
||||
use crate::core::db::{PostgresDb, VideoRecord, VideoStatus};
|
||||
|
||||
pub struct WatcherConfig {
|
||||
pub directories: Vec<String>,
|
||||
pub poll_interval_ms: u64,
|
||||
@@ -20,7 +23,7 @@ impl Default for WatcherConfig {
|
||||
}
|
||||
|
||||
/// Starts the file watcher in the background.
|
||||
/// Detects new files and logs them. Does NOT auto-register or auto-pre-process.
|
||||
/// Detects new files and logs them.
|
||||
pub async fn run_watcher() -> Result<()> {
|
||||
let config = WatcherConfig::default();
|
||||
let dirs = config.directories.clone();
|
||||
@@ -35,7 +38,6 @@ pub async fn run_watcher() -> Result<()> {
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut interval = time::interval(std::time::Duration::from_millis(config.poll_interval_ms));
|
||||
// Track known files across cycles
|
||||
let mut known = std::collections::HashSet::new();
|
||||
loop {
|
||||
interval.tick().await;
|
||||
@@ -70,16 +72,97 @@ async fn report_new_files(directories: &[String], known: &mut std::collections::
|
||||
current_files.insert(fname.clone());
|
||||
if !known.contains(&fname) {
|
||||
info!("[WATCHER] New file detected: {} in {}", fname, dir);
|
||||
if crate::core::config::get_watcher_auto_register() {
|
||||
let fpath = file_path.to_string_lossy().to_string();
|
||||
tokio::spawn(async move {
|
||||
auto_register_file(&fpath).await;
|
||||
});
|
||||
}
|
||||
known.insert(fname);
|
||||
}
|
||||
}
|
||||
// Remove files that no longer exist (deleted between cycles)
|
||||
known.retain(|k| current_files.contains(k));
|
||||
}
|
||||
}
|
||||
|
||||
async fn auto_register_file(file_path: &str) {
|
||||
let file_uuid = match pre_process_file(file_path).await {
|
||||
Some(u) => u,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let output_dir = std::env::var("MOMENTRY_OUTPUT_DIR")
|
||||
.unwrap_or_else(|_| "/Users/accusys/momentry/output_dev".to_string());
|
||||
let pre_path = std::path::PathBuf::from(&output_dir).join(format!("{}.pre.json", file_uuid));
|
||||
let pre_content = match std::fs::read_to_string(&pre_path) {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
warn!("[WATCHER] Failed to read pre.json: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let pre: serde_json::Value = match serde_json::from_str(&pre_content) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
warn!("[WATCHER] Failed to parse pre.json: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let file_name = pre.get("file_name").and_then(|v| v.as_str()).unwrap_or("unknown").to_string();
|
||||
let probe = pre.get("probe_json").cloned().unwrap_or_default();
|
||||
let file_type = pre.get("file_type").and_then(|v| v.as_str()).unwrap_or("unknown").to_string();
|
||||
let canonical_path = pre.get("file_path").and_then(|v| v.as_str()).unwrap_or(file_path).to_string();
|
||||
|
||||
let duration = probe.get("format").and_then(|f| f.get("duration")).and_then(|v| v.as_f64()).unwrap_or(0.0);
|
||||
let width = probe.get("format").and_then(|f| f.get("width")).and_then(|v| v.as_u64()).unwrap_or(0) as u32;
|
||||
let height = probe.get("format").and_then(|f| f.get("height")).and_then(|v| v.as_u64()).unwrap_or(0) as u32;
|
||||
let fps_val = probe.get("format").and_then(|f| f.get("fps")).and_then(|v| v.as_f64()).unwrap_or(0.0);
|
||||
|
||||
let record = VideoRecord {
|
||||
id: 0,
|
||||
file_uuid,
|
||||
file_path: canonical_path,
|
||||
file_name,
|
||||
file_type: Some(file_type),
|
||||
duration,
|
||||
width,
|
||||
height,
|
||||
fps: fps_val,
|
||||
probe_json: Some(probe),
|
||||
storage: Default::default(),
|
||||
status: VideoStatus::Registered,
|
||||
processing_status: None,
|
||||
birth_registration: None,
|
||||
user_id: None,
|
||||
job_id: None,
|
||||
created_at: String::new(),
|
||||
registration_time: None,
|
||||
total_frames: 0,
|
||||
parent_uuid: None,
|
||||
cut_done: false,
|
||||
cut_count: 0,
|
||||
cut_max_duration: 0.0,
|
||||
scene_done: false,
|
||||
audio_tracks: None,
|
||||
};
|
||||
|
||||
let database_url = crate::core::config::DATABASE_URL.as_str();
|
||||
let db = match PostgresDb::new(database_url).await {
|
||||
Ok(d) => d,
|
||||
Err(e) => {
|
||||
warn!("[WATCHER] Failed to connect DB for auto-register: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
match db.register_video(&record).await {
|
||||
Ok(id) => info!("[WATCHER] Auto-registered {} (id={})", record.file_uuid, id),
|
||||
Err(e) => warn!("[WATCHER] Auto-register failed for {}: {}", record.file_uuid, e),
|
||||
}
|
||||
}
|
||||
|
||||
/// Pre-process a single file: compute SHA256 + probe + UUID → .pre.json
|
||||
/// This is called explicitly from register API, NOT from the watcher.
|
||||
pub async fn pre_process_file(file_path: &str) -> Option<String> {
|
||||
let path = std::path::Path::new(file_path);
|
||||
if !path.is_file() {
|
||||
|
||||
Reference in New Issue
Block a user