mod settings; use settings::Settings; use std::sync::mpsc; use directories::ProjectDirs; use std::path::PathBuf; use anyhow::Result; use thiserror::Error; use kordophone_db::{ database::Database, models::Conversation, repository::Repository, }; use kordophone::model::JwtToken; use kordophone::api::{ http_client::{Credentials, HTTPAPIClient}, APIInterface, TokenManagement, }; pub enum Event { SyncAllConversations, } #[derive(Debug, Error)] pub enum DaemonError { #[error("Client Not Configured")] ClientNotConfigured, } pub struct Daemon { pub version: String, database: Database, } impl Daemon { pub fn new() -> Result { let database_path = Self::get_database_path(); log::info!("Database path: {}", database_path.display()); // Create the database directory if it doesn't exist let database_dir = database_path.parent().unwrap(); std::fs::create_dir_all(database_dir)?; let database = Database::new(&database_path.to_string_lossy())?; Ok(Self { version: "0.1.0".to_string(), database }) } pub fn get_conversations(&mut self) -> Vec { self.database.with_repository(|r| r.all_conversations().unwrap()) } pub async fn sync_all_conversations(&mut self) -> Result<()> { let mut client = self.get_client() .map_err(|_| DaemonError::ClientNotConfigured)?; let fetched_conversations = client.get_conversations().await?; let db_conversations: Vec = fetched_conversations.into_iter() .map(|c| kordophone_db::models::Conversation::from(c)) .collect(); // Process each conversation let mut repository = Repository::new(&mut self.database.connection); for conversation in db_conversations { let conversation_id = conversation.guid.clone(); // Insert the conversation repository.insert_conversation(conversation)?; // Fetch and sync messages for this conversation let messages = client.get_messages(&conversation_id).await?; let db_messages: Vec = messages.into_iter() .map(|m| kordophone_db::models::Message::from(m)) .collect(); // Insert each message for message in db_messages { repository.insert_message(&conversation_id, message)?; } } Ok(()) } pub fn get_settings(&mut self) -> Result { let settings = self.database.with_settings(|s| Settings::from_db(s) )?; Ok(settings) } pub async fn handle_event(&mut self, event: Event) { match event { Event::SyncAllConversations => { self.sync_all_conversations().await.unwrap_or_else(|e| { log::error!("Error handling sync event: {}", e); }); } } } fn get_client(&mut self) -> Result { let settings = self.database.with_settings(|s| Settings::from_db(s) )?; let server_url = settings.server_url .ok_or(DaemonError::ClientNotConfigured)?; let client = HTTPAPIClient::new( server_url.parse().unwrap(), match (settings.username, settings.credential_item) { (Some(username), Some(password)) => Some( Credentials { username, password, } ), _ => None, } ); Ok(client) } fn get_database_path() -> PathBuf { if let Some(proj_dirs) = ProjectDirs::from("net", "buzzert", "kordophonecd") { let data_dir = proj_dirs.data_dir(); data_dir.join("database.db") } else { // Fallback to a local path if we can't get the system directories PathBuf::from("database.db") } } } impl TokenManagement for &mut Daemon { fn get_token(&mut self) -> Option { self.database.get_token() } fn set_token(&mut self, token: JwtToken) { self.database.set_token(token); } }