use anyhow::Result;
use std::fs;
use std::time::Duration;
use chrono::Local;
use crate::schedule_config::{ScheduleConfig, JobConfig, WindowConfig};
use crate::worker::{PlaywrightWorker, ScrapingEngine, ScrapeOptions};
use crate::clustering;
use crate::cache::LocalCache;
pub async fn run_scheduler_daemon() -> Result<()> {
println!("⏰ [SCHEDULER] Iniciando Daemon del Planificador de Tareas...");
let config = ScheduleConfig::load_or_create()?;
println!("⏰ [SCHEDULER] Cargadas {} tareas de agenda recurrentes.", config.jobs.len());
loop {
if let Err(e) = check_and_execute_jobs(&config).await {
eprintln!("⚠️ [SCHEDULER] Error durante la comprobación de tareas: {}", e);
}
tokio::time::sleep(Duration::from_secs(60)).await;
}
}
async fn check_and_execute_jobs(config: &ScheduleConfig) -> Result<()> {
let now = Local::now();
let weekday_str = now.format("%a").to_string(); let current_time_str = now.format("%H:%M").to_string(); let date_str = now.format("%Y-%m-%d").to_string();
for job in &config.jobs {
if !job.days.contains(&weekday_str) {
continue;
}
for window in &job.windows {
if is_time_in_window(¤t_time_str, &window.start, &window.end) {
let cache = LocalCache::new()?;
let schedule_key = format!("schedule:{}:{}:{}", job.name, date_str, window.name);
match cache.get(&schedule_key) {
Ok(Some(status)) if status == "EJECUTADO" => {
continue;
}
_ => {
println!("⏰ [SCHEDULER] Ventana horaria '{}' activa para la tarea '{}'. Iniciando ejecución...", window.name, job.name);
execute_scheduled_job(job, window, &date_str, &cache).await?;
}
}
}
}
}
Ok(())
}
fn is_time_in_window(current: &str, start: &str, end: &str) -> bool {
current >= start && current <= end
}
async fn execute_scheduled_job(
job: &JobConfig,
window: &WindowConfig,
date_str: &str,
cache: &LocalCache,
) -> Result<()> {
let mut worker = PlaywrightWorker::new(true, ScrapingEngine::Playwright)?;
println!("⏰ [SCHEDULER] Navegando y extrayendo de: {}", job.url);
let scrape_opts = ScrapeOptions {
deep_scan: false,
semantic_embeddings: Some(true),
text: None,
prompt: None,
};
let resp = worker.scrape(&job.url, Some(scrape_opts))?;
if resp.status == "success" {
let markdown = resp.markdown.clone().unwrap_or_default();
let compressed_text = if let (Some(sentences), Some(embeddings)) = (&resp.sentences, &resp.embeddings) {
println!("⏰ [SCHEDULER] Comprimiendo texto por clustering K-Means...");
let summary_sentences = clustering::compress_text_by_clustering(sentences, embeddings, 12);
summary_sentences.join("\n")
} else {
markdown.chars().take(2000).collect()
};
println!("⏰ [SCHEDULER] Ejecutando extracción estructurada JSON...");
let extract_opts = ScrapeOptions {
deep_scan: false,
semantic_embeddings: None,
text: Some(compressed_text),
prompt: Some(job.extract_prompt.clone()),
};
let extract_resp = worker.scrape("extract", Some(extract_opts))?;
if extract_resp.status == "success" {
if let Some(data) = extract_resp.data {
fs::create_dir_all("downloads")?;
fs::write(&job.output_path, serde_json::to_string_pretty(&data)?)?;
println!("⏰ [SCHEDULER] Tarea programada '{}' completada de forma exitosa. Guardado en: {}", job.name, job.output_path);
let schedule_key = format!("schedule:{}:{}:{}", job.name, date_str, window.name);
cache.set(&schedule_key, "EJECUTADO")?;
}
} else {
eprintln!("⚠️ [SCHEDULER] La extracción semántica falló: {}", extract_resp.message.unwrap_or_default());
}
} else {
eprintln!("⚠️ [SCHEDULER] Fallo al navegar a la URL: {}", resp.message.unwrap_or_default());
}
worker.exit()?;
Ok(())
}