From fe32efef2c2b11d5be0cfcaf07c4fd7ff9f4e361 Mon Sep 17 00:00:00 2001 From: James Magahern Date: Fri, 25 Apr 2025 18:02:54 -0700 Subject: [PATCH] daemon: scaffolding for settings / sync --- Cargo.lock | 1 + kordophoned/Cargo.toml | 1 + .../net.buzzert.kordophonecd.Server.xml | 38 +++++++++- kordophoned/src/daemon/mod.rs | 28 +++++-- kordophoned/src/dbus/endpoint.rs | 38 ++++++---- kordophoned/src/dbus/mod.rs | 6 +- kordophoned/src/dbus/server_impl.rs | 74 +++++++++++++++++-- kordophoned/src/main.rs | 37 +++++++--- kpcli/src/daemon/mod.rs | 24 +++++- kpcli/src/main.rs | 2 +- 10 files changed, 204 insertions(+), 45 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6679ace..12cbe08 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -851,6 +851,7 @@ dependencies = [ "kordophone", "kordophone-db", "log", + "thiserror", "tokio", ] diff --git a/kordophoned/Cargo.toml b/kordophoned/Cargo.toml index 7af3d47..d5c6ac7 100644 --- a/kordophoned/Cargo.toml +++ b/kordophoned/Cargo.toml @@ -14,6 +14,7 @@ env_logger = "0.11.6" kordophone = { path = "../kordophone" } kordophone-db = { path = "../kordophone-db" } log = "0.4.25" +thiserror = "2.0.12" tokio = { version = "1", features = ["full"] } [build-dependencies] diff --git a/kordophoned/include/net.buzzert.kordophonecd.Server.xml b/kordophoned/include/net.buzzert.kordophonecd.Server.xml index 954009f..760eda9 100644 --- a/kordophoned/include/net.buzzert.kordophonecd.Server.xml +++ b/kordophoned/include/net.buzzert.kordophonecd.Server.xml @@ -1,6 +1,7 @@ - - - + + + @@ -15,5 +16,36 @@ 'is_unread' (boolean): Unread status"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/kordophoned/src/daemon/mod.rs b/kordophoned/src/daemon/mod.rs index 178abac..751d639 100644 --- a/kordophoned/src/daemon/mod.rs +++ b/kordophoned/src/daemon/mod.rs @@ -1,16 +1,24 @@ use directories::ProjectDirs; use std::path::PathBuf; use anyhow::Result; - +use thiserror::Error; use kordophone_db::{ database::Database, - settings::Settings, models::Conversation, }; +use kordophone::api::http_client::HTTPAPIClient; + +#[derive(Debug, Error)] +pub enum DaemonError { + #[error("Client Not Configured")] + ClientNotConfigured, +} + pub struct Daemon { pub version: String, database: Database, + client: Option, } impl Daemon { @@ -23,17 +31,25 @@ impl Daemon { 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_version(&self) -> String { - self.version.clone() + // TODO: Check to see if we have client settings in the database + + + Ok(Self { version: "0.1.0".to_string(), database, client: None }) } pub fn get_conversations(&mut self) -> Vec { self.database.with_repository(|r| r.all_conversations().unwrap()) } + pub fn sync_all_conversations(&mut self) -> Result<()> { + let client = self.client + .as_mut() + .ok_or(DaemonError::ClientNotConfigured)?; + + Ok(()) + } + fn get_database_path() -> PathBuf { if let Some(proj_dirs) = ProjectDirs::from("com", "kordophone", "kordophone") { let data_dir = proj_dirs.data_dir(); diff --git a/kordophoned/src/dbus/endpoint.rs b/kordophoned/src/dbus/endpoint.rs index e1f3cce..8e9d4f6 100644 --- a/kordophoned/src/dbus/endpoint.rs +++ b/kordophoned/src/dbus/endpoint.rs @@ -1,6 +1,5 @@ use log::info; use std::sync::{Arc, Mutex}; -use crate::{daemon::Daemon, dbus::interface}; use dbus_crossroads::Crossroads; use dbus_tokio::connection; @@ -11,13 +10,13 @@ use dbus::{ Path, }; -pub struct Endpoint { +pub struct Endpoint { connection: Arc, - daemon: Arc>, + implementation: T, } -impl Endpoint { - pub fn new(daemon: Daemon) -> Self { +impl Endpoint { + pub fn new(implementation: T) -> Self { let (resource, connection) = connection::new_session_sync().unwrap(); // The resource is a task that should be spawned onto a tokio compatible @@ -31,15 +30,24 @@ impl Endpoint { Self { connection, - daemon: Arc::new(Mutex::new(daemon)) + implementation } } - pub async fn start(&self) { - use crate::dbus::interface; + pub async fn register( + &self, + name: &str, + path: &str, + register_fn: F + ) + where + F: Fn(&mut Crossroads) -> R, + R: IntoIterator>, + { + let dbus_path = String::from(path); self.connection - .request_name(interface::NAME, false, true, false) + .request_name(name, false, true, false) .await .expect("Unable to acquire dbus name"); @@ -54,9 +62,9 @@ impl Endpoint { }), ))); - // Register the daemon as a D-Bus object. - let token = interface::register_net_buzzert_kordophone_server(&mut cr); - cr.insert(interface::OBJECT_PATH, &[token], self.daemon.clone()); + // Register the daemon as a D-Bus object with multiple interfaces + let tokens: Vec<_> = register_fn(&mut cr).into_iter().collect(); + cr.insert(dbus_path, &tokens, self.implementation.clone()); // Start receiving messages. self.connection.start_receive( @@ -66,14 +74,14 @@ impl Endpoint { ), ); - info!(target: "dbus", "DBus server started"); + info!(target: "dbus", "Registered endpoint at {} with {} interfaces", path, tokens.len()); } - pub fn send_signal(&self, signal: S) -> Result + pub fn send_signal(&self, path: &str, signal: S) -> Result where S: dbus::message::SignalArgs + dbus::arg::AppendAll, { - let message = signal.to_emit_message(&Path::new(interface::OBJECT_PATH).unwrap()); + let message = signal.to_emit_message(&Path::new(path).unwrap()); self.connection.send(message) } } diff --git a/kordophoned/src/dbus/mod.rs b/kordophoned/src/dbus/mod.rs index ff97742..66c8455 100644 --- a/kordophoned/src/dbus/mod.rs +++ b/kordophoned/src/dbus/mod.rs @@ -1,11 +1,11 @@ pub mod endpoint; -mod server_impl; +pub mod server_impl; -mod interface { +pub mod interface { #![allow(unused)] pub const NAME: &str = "net.buzzert.kordophonecd"; - pub const OBJECT_PATH: &str = "/net/buzzert/kordophonecd"; + pub const OBJECT_PATH: &str = "/net/buzzert/kordophonecd/daemon"; include!(concat!(env!("OUT_DIR"), "/kordophone-server.rs")); } \ No newline at end of file diff --git a/kordophoned/src/dbus/server_impl.rs b/kordophoned/src/dbus/server_impl.rs index a1dae48..f107bd3 100644 --- a/kordophoned/src/dbus/server_impl.rs +++ b/kordophoned/src/dbus/server_impl.rs @@ -1,19 +1,36 @@ use dbus::arg; use dbus_tree::MethodErr; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, Mutex, MutexGuard}; +use log::info; use crate::daemon::Daemon; -use crate::dbus::interface::NetBuzzertKordophoneServer as DbusServer; +use crate::dbus::interface::NetBuzzertKordophoneRepository as DbusRepository; +use crate::dbus::interface::NetBuzzertKordophoneSettings as DbusSettings; -impl DbusServer for Arc> { +#[derive(Clone)] +pub struct ServerImpl { + daemon: Arc>, +} + +impl ServerImpl { + pub fn new(daemon: Arc>) -> Self { + Self { daemon } + } + + pub fn get_daemon(&self) -> Result, MethodErr> { + self.daemon.lock().map_err(|_| MethodErr::failed("Failed to lock daemon")) + } +} + +impl DbusRepository for ServerImpl { fn get_version(&mut self) -> Result { - let daemon = self.lock().map_err(|_| MethodErr::failed("Failed to lock daemon"))?; + let daemon = self.get_daemon()?; Ok(daemon.version.clone()) } fn get_conversations(&mut self) -> Result, dbus::MethodErr> { // Get a repository instance and use it to fetch conversations - let mut daemon = self.lock().map_err(|_| MethodErr::failed("Failed to lock daemon"))?; + let mut daemon = self.get_daemon()?; let conversations = daemon.get_conversations(); // Convert conversations to DBus property maps @@ -27,4 +44,49 @@ impl DbusServer for Arc> { Ok(result) } -} \ No newline at end of file + + fn sync_all_conversations(&mut self) -> Result { + let mut daemon = self.get_daemon()?; + daemon.sync_all_conversations().map_err(|e| { + log::error!("Failed to sync conversations: {}", e); + MethodErr::failed(&format!("Failed to sync conversations: {}", e)) + })?; + + Ok(true) + } +} + +impl DbusSettings for ServerImpl { + fn set_server(&mut self, url: String, user: String) -> Result<(), dbus::MethodErr> { + todo!() + } + + fn set_credential_item_(&mut self, item_path: dbus::Path<'static>) -> Result<(), dbus::MethodErr> { + todo!() + } + + fn server_url(&self) -> Result { + todo!() + } + + fn set_server_url(&self, value: String) -> Result<(), dbus::MethodErr> { + todo!() + } + + fn username(&self) -> Result { + todo!() + } + + fn set_username(&self, value: String) -> Result<(), dbus::MethodErr> { + todo!() + } + + fn credential_item(&self) -> Result, dbus::MethodErr> { + todo!() + } + + fn set_credential_item(&self, value: dbus::Path<'static>) -> Result<(), dbus::MethodErr> { + todo!() + } + +} diff --git a/kordophoned/src/main.rs b/kordophoned/src/main.rs index a63db94..e6772e5 100644 --- a/kordophoned/src/main.rs +++ b/kordophoned/src/main.rs @@ -2,10 +2,13 @@ mod dbus; mod daemon; use std::future; +use std::sync::{Arc, Mutex}; use log::LevelFilter; use daemon::Daemon; use dbus::endpoint::Endpoint as DbusEndpoint; +use dbus::interface; +use dbus::server_impl::ServerImpl; fn initialize_logging() { env_logger::Builder::from_default_env() @@ -19,16 +22,32 @@ async fn main() { initialize_logging(); // Create the daemon - let daemon = Daemon::new() - .map_err(|e| { - log::error!("Failed to start daemon: {}", e); - std::process::exit(1); - }) - .unwrap(); + let daemon = Arc::new( + Mutex::new( + Daemon::new() + .map_err(|e| { + log::error!("Failed to start daemon: {}", e); + std::process::exit(1); + }) + .unwrap() + ) + ); - // Create the D-Bus endpoint - let endpoint = DbusEndpoint::new(daemon); - endpoint.start().await; + // Create the server implementation + let server = ServerImpl::new(daemon); + + // Register DBus interfaces with endpoint + let endpoint = DbusEndpoint::new(server.clone()); + endpoint.register( + interface::NAME, + interface::OBJECT_PATH, + |cr| { + vec![ + interface::register_net_buzzert_kordophone_repository(cr), + interface::register_net_buzzert_kordophone_settings(cr) + ] + } + ).await; future::pending::<()>().await; unreachable!() diff --git a/kpcli/src/daemon/mod.rs b/kpcli/src/daemon/mod.rs index 8e5ae49..e7de320 100644 --- a/kpcli/src/daemon/mod.rs +++ b/kpcli/src/daemon/mod.rs @@ -10,10 +10,16 @@ mod dbus_interface { include!(concat!(env!("OUT_DIR"), "/kordophone-client.rs")); } -use dbus_interface::NetBuzzertKordophoneServer as KordophoneServer; +use dbus_interface::NetBuzzertKordophoneRepository as KordophoneRepository; #[derive(Subcommand)] pub enum Commands { + /// Gets all known conversations. + Conversations, + + /// Runs a sync operation. + Sync, + /// Prints the server Kordophone version. Version, } @@ -23,6 +29,8 @@ impl Commands { let mut client = DaemonCli::new()?; match cmd { Commands::Version => client.print_version().await, + Commands::Conversations => client.print_conversations().await, + Commands::Sync => client.sync_conversations().await, } } } @@ -43,8 +51,20 @@ impl DaemonCli { } pub async fn print_version(&mut self) -> Result<()> { - let version = KordophoneServer::get_version(&self.proxy())?; + let version = KordophoneRepository::get_version(&self.proxy())?; println!("Server version: {}", version); Ok(()) } + + pub async fn print_conversations(&mut self) -> Result<()> { + let conversations = KordophoneRepository::get_conversations(&self.proxy())?; + println!("Conversations: {:?}", conversations); + Ok(()) + } + + pub async fn sync_conversations(&mut self) -> Result<()> { + let success = KordophoneRepository::sync_all_conversations(&self.proxy())?; + println!("Synced conversations: {}", success); + Ok(()) + } } \ No newline at end of file diff --git a/kpcli/src/main.rs b/kpcli/src/main.rs index 7445699..e0f7743 100644 --- a/kpcli/src/main.rs +++ b/kpcli/src/main.rs @@ -48,6 +48,6 @@ async fn main() { let cli = Cli::parse(); run_command(cli.command).await - .map_err(|e| log::error!("Error: {}", e)) + .map_err(|e| println!("Error: {}", e)) .err(); }