use chrono::{Datelike, Duration, NaiveDate, Utc}; use indicatif::{ProgressBar, ProgressStyle}; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; use std::collections::HashMap; use std::fs::{self, File}; use std::io::{Read, Write}; use std::path::Path; // Example API response type #[derive(Debug, Serialize, Deserialize, Clone)] struct HistoryItem { checkpoint: Option, datetime: String, sha256: Option, filename: Option, hostname: Option, // other fields... } // JSON file structure #[derive(Debug, Serialize, Deserialize)] struct JsonFile { error: String, response: ResponseData, } #[derive(Debug, Serialize, Deserialize)] struct ResponseData { exechistories: Vec, } // Mock API function fn history_logging(_type: &str, checkpoint: &str, _policy: &[&str]) -> Vec { // Replace with actual API call vec![] } fn main() { let file_path = Path::new("data/example.json"); let mut checkpoint = "initial_checkpoint".to_string(); let policy_name = "policy1".to_string(); let days = 7; // Initialize JSON output let mut json_output = JsonFile { error: "Success".to_string(), response: ResponseData { exechistories: vec![], }, }; // Outer progress bar (filebar) let filebar = ProgressBar::new(10_000); filebar.set_style( ProgressStyle::default_bar() .template("Checkpoint Progress: {msg} [{bar:40.cyan/blue}] {pos}/{len}") .unwrap(), ); filebar.set_message(&checkpoint); // Inner progress bar (total progress) let pbar = ProgressBar::new(100); pbar.set_style( ProgressStyle::default_bar() .template("Total of {msg} Complete: [{bar:40.cyan/blue}] {pos}/{len}") .unwrap(), ); pbar.set_message(&policy_name); loop { let histories = history_logging("type", &checkpoint, &[&policy_name]); if !histories.iter().all(|h| h.datetime.len() > 0) { eprintln!("Unexpected response format from API."); break; } filebar.set_length(histories.len() as u64); if histories.is_empty() { break; } for (index, history_item) in histories.iter().enumerate() { if history_item.checkpoint.is_none() || history_item.datetime.is_empty() { continue; } if index == histories.len() - 1 { checkpoint = history_item.checkpoint.clone().unwrap(); filebar.set_message(&checkpoint); break; } // Parse date let history_date = match NaiveDate::parse_from_str( &history_item.datetime.replace(" +0000 UTC", ""), "%Y-%m-%dT%H:%M:%SZ", ) { Ok(date) => date, Err(_) => continue, }; let cutoff = Utc::today().naive_utc() - Duration::days(days); if history_date >= cutoff { json_output.response.exechistories.push(history_item.clone()); } filebar.inc(1); filebar.tick(); } // Deduplicate let mut seen: HashMap<(Option, Option, Option), HistoryItem> = HashMap::new(); let combined = if file_path.exists() { let mut f = File::open(file_path).unwrap(); let mut contents = String::new(); f.read_to_string(&mut contents).unwrap(); let existing_data: JsonFile = serde_json::from_str(&contents).unwrap_or(JsonFile { error: "Success".to_string(), response: ResponseData { exechistories: vec![], }, }); [existing_data.response.exechistories, json_output.response.exechistories.clone()] .concat() } else { json_output.response.exechistories.clone() }; for entry in combined { let key = (entry.sha256.clone(), entry.filename.clone(), entry.hostname.clone()); seen.insert(key, entry); } let deduplicated: Vec = seen.into_values().collect(); // Write to file let output_file = File::create(file_path).unwrap(); serde_json::to_writer_pretty(&output_file, &json!({ "error": "Success", "response": { "exechistories": deduplicated } })) .unwrap(); json_output.response.exechistories.clear(); // Update inner progress bar (percentage based on last valid item) if let Some(last_item) = histories.last() { if let Ok(last_date) = NaiveDate::parse_from_str( &last_item.datetime.replace(" +0000 UTC", ""), "%Y-%m-%dT%H:%M:%SZ", ) { let date_diff = Utc::today().naive_utc() - last_date; let percentage_diff = ((days + 10) - date_diff.num_days()) as f64 / (days + 10) as f64 * 100.0; pbar.set_position(percentage_diff.round() as u64); pbar.set_message(&policy_name); pbar.tick(); } } filebar.set_position(1); } filebar.finish(); pbar.finish(); }