#![allow(dead_code)]
use crate::error::{GdeltError, Result};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DaemonState {
pub pid: Option<u32>,
pub running: bool,
pub last_sync: Option<DateTime<Utc>>,
pub next_sync: Option<DateTime<Utc>>,
pub sync_interval_secs: u64,
pub mcp_enabled: bool,
pub mcp_port: u16,
pub sync_status: SyncStatus,
pub last_error: Option<String>,
pub started_at: Option<DateTime<Utc>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct SyncStatus {
pub files_synced: u64,
pub events_imported: u64,
pub gkg_imported: u64,
pub total_syncs: u64,
pub sync_failures: u64,
}
impl Default for DaemonState {
fn default() -> Self {
Self {
pid: None,
running: false,
last_sync: None,
next_sync: None,
sync_interval_secs: 15 * 60, mcp_enabled: true,
mcp_port: 8080,
sync_status: SyncStatus::default(),
last_error: None,
started_at: None,
}
}
}
impl DaemonState {
pub fn state_file_path() -> Result<PathBuf> {
let data_dir = crate::config::data_dir()
.ok_or_else(|| GdeltError::Config("Could not determine data directory".into()))?;
Ok(data_dir.join("daemon.json"))
}
pub fn pid_file_path() -> Result<PathBuf> {
let data_dir = crate::config::data_dir()
.ok_or_else(|| GdeltError::Config("Could not determine data directory".into()))?;
Ok(data_dir.join("daemon.pid"))
}
pub fn log_file_path() -> Result<PathBuf> {
let data_dir = crate::config::data_dir()
.ok_or_else(|| GdeltError::Config("Could not determine data directory".into()))?;
Ok(data_dir.join("daemon.log"))
}
pub fn load() -> Result<Self> {
let path = Self::state_file_path()?;
if path.exists() {
let content = std::fs::read_to_string(&path)?;
let state: DaemonState = serde_json::from_str(&content)?;
Ok(state)
} else {
Ok(Self::default())
}
}
pub fn save(&self) -> Result<()> {
let path = Self::state_file_path()?;
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
let content = serde_json::to_string_pretty(self)?;
std::fs::write(path, content)?;
Ok(())
}
pub fn is_actually_running(&self) -> bool {
if let Some(pid) = self.pid {
#[cfg(unix)]
{
use std::process::Command;
Command::new("kill")
.args(["-0", &pid.to_string()])
.output()
.map(|o| o.status.success())
.unwrap_or(false)
}
#[cfg(not(unix))]
{
false
}
} else {
false
}
}
pub fn mark_started(&mut self, pid: u32) {
self.pid = Some(pid);
self.running = true;
self.started_at = Some(Utc::now());
self.last_error = None;
}
pub fn mark_stopped(&mut self) {
self.pid = None;
self.running = false;
}
pub fn record_sync_success(&mut self, files: u64, events: u64, gkg: u64) {
self.last_sync = Some(Utc::now());
self.next_sync = Some(Utc::now() + chrono::Duration::seconds(self.sync_interval_secs as i64));
self.sync_status.files_synced = files;
self.sync_status.events_imported = events;
self.sync_status.gkg_imported = gkg;
self.sync_status.total_syncs += 1;
self.last_error = None;
}
pub fn record_sync_failure(&mut self, error: &str) {
self.sync_status.sync_failures += 1;
self.last_error = Some(error.to_string());
}
}