daemon: implements post office
This commit is contained in:
61
Cargo.lock
generated
61
Cargo.lock
generated
@@ -1036,6 +1036,8 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.12",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tokio-condvar",
|
||||||
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1386,35 +1388,14 @@ version = "5.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
|
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rand"
|
|
||||||
version = "0.8.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
"rand_chacha 0.3.1",
|
|
||||||
"rand_core 0.6.4",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand"
|
name = "rand"
|
||||||
version = "0.9.1"
|
version = "0.9.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97"
|
checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rand_chacha 0.9.0",
|
"rand_chacha",
|
||||||
"rand_core 0.9.3",
|
"rand_core",
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rand_chacha"
|
|
||||||
version = "0.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
|
||||||
dependencies = [
|
|
||||||
"ppv-lite86",
|
|
||||||
"rand_core 0.6.4",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1424,16 +1405,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ppv-lite86",
|
"ppv-lite86",
|
||||||
"rand_core 0.9.3",
|
"rand_core",
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rand_core"
|
|
||||||
version = "0.6.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
|
||||||
dependencies = [
|
|
||||||
"getrandom 0.2.14",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1821,6 +1793,15 @@ dependencies = [
|
|||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-condvar"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e8530e402d24f6a65019baa57593f1769557c670302f493cdf8fa3dfbe4d85ac"
|
||||||
|
dependencies = [
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-macros"
|
name = "tokio-macros"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
@@ -1944,7 +1925,7 @@ dependencies = [
|
|||||||
"http 1.3.1",
|
"http 1.3.1",
|
||||||
"httparse",
|
"httparse",
|
||||||
"log",
|
"log",
|
||||||
"rand 0.9.1",
|
"rand",
|
||||||
"sha1",
|
"sha1",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.12",
|
||||||
"utf-8",
|
"utf-8",
|
||||||
@@ -1988,20 +1969,20 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "1.11.0"
|
version = "1.16.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a"
|
checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom 0.2.14",
|
"getrandom 0.3.2",
|
||||||
"rand 0.8.5",
|
"rand",
|
||||||
"uuid-macro-internal",
|
"uuid-macro-internal",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid-macro-internal"
|
name = "uuid-macro-internal"
|
||||||
version = "1.11.0"
|
version = "1.16.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6b91f57fe13a38d0ce9e28a03463d8d3c2468ed03d75375110ec71d93b449a08"
|
checksum = "72dcd78c4f979627a754f5522cea6e6a25e55139056535fe6e69c506cd64a862"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|||||||
@@ -227,7 +227,7 @@ impl<K: AuthenticationStore + Send + Sync> APIInterface for HTTPAPIClient<K> {
|
|||||||
|
|
||||||
async fn send_message(
|
async fn send_message(
|
||||||
&mut self,
|
&mut self,
|
||||||
outgoing_message: OutgoingMessage,
|
outgoing_message: &OutgoingMessage,
|
||||||
) -> Result<Message, Self::Error> {
|
) -> Result<Message, Self::Error> {
|
||||||
let message: Message = self.request_with_body(
|
let message: Message = self.request_with_body(
|
||||||
"sendMessage",
|
"sendMessage",
|
||||||
|
|||||||
@@ -15,10 +15,11 @@ pub mod event_socket;
|
|||||||
pub use event_socket::EventSocket;
|
pub use event_socket::EventSocket;
|
||||||
|
|
||||||
use self::http_client::Credentials;
|
use self::http_client::Credentials;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait APIInterface {
|
pub trait APIInterface {
|
||||||
type Error;
|
type Error: Debug;
|
||||||
|
|
||||||
// (GET) /version
|
// (GET) /version
|
||||||
async fn get_version(&mut self) -> Result<String, Self::Error>;
|
async fn get_version(&mut self) -> Result<String, Self::Error>;
|
||||||
@@ -38,7 +39,7 @@ pub trait APIInterface {
|
|||||||
// (POST) /sendMessage
|
// (POST) /sendMessage
|
||||||
async fn send_message(
|
async fn send_message(
|
||||||
&mut self,
|
&mut self,
|
||||||
outgoing_message: OutgoingMessage,
|
outgoing_message: &OutgoingMessage,
|
||||||
) -> Result<Message, Self::Error>;
|
) -> Result<Message, Self::Error>;
|
||||||
|
|
||||||
// (POST) /authenticate
|
// (POST) /authenticate
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use super::conversation::ConversationID;
|
use super::conversation::ConversationID;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize)]
|
#[derive(Debug, Clone, Serialize)]
|
||||||
pub struct OutgoingMessage {
|
pub struct OutgoingMessage {
|
||||||
|
#[serde(skip)]
|
||||||
|
pub guid: Uuid,
|
||||||
|
|
||||||
#[serde(rename = "body")]
|
#[serde(rename = "body")]
|
||||||
pub text: String,
|
pub text: String,
|
||||||
|
|
||||||
@@ -21,6 +25,7 @@ impl OutgoingMessage {
|
|||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct OutgoingMessageBuilder {
|
pub struct OutgoingMessageBuilder {
|
||||||
|
guid: Option<Uuid>,
|
||||||
text: Option<String>,
|
text: Option<String>,
|
||||||
conversation_id: Option<ConversationID>,
|
conversation_id: Option<ConversationID>,
|
||||||
file_transfer_guids: Option<Vec<String>>,
|
file_transfer_guids: Option<Vec<String>>,
|
||||||
@@ -31,6 +36,11 @@ impl OutgoingMessageBuilder {
|
|||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn guid(mut self, guid: Uuid) -> Self {
|
||||||
|
self.guid = Some(guid);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn text(mut self, text: String) -> Self {
|
pub fn text(mut self, text: String) -> Self {
|
||||||
self.text = Some(text);
|
self.text = Some(text);
|
||||||
self
|
self
|
||||||
@@ -48,6 +58,7 @@ impl OutgoingMessageBuilder {
|
|||||||
|
|
||||||
pub fn build(self) -> OutgoingMessage {
|
pub fn build(self) -> OutgoingMessage {
|
||||||
OutgoingMessage {
|
OutgoingMessage {
|
||||||
|
guid: self.guid.unwrap_or_else(|| Uuid::new_v4()),
|
||||||
text: self.text.unwrap(),
|
text: self.text.unwrap(),
|
||||||
conversation_id: self.conversation_id.unwrap(),
|
conversation_id: self.conversation_id.unwrap(),
|
||||||
file_transfer_guids: self.file_transfer_guids.unwrap_or_default(),
|
file_transfer_guids: self.file_transfer_guids.unwrap_or_default(),
|
||||||
|
|||||||
@@ -93,15 +93,15 @@ impl APIInterface for TestClient {
|
|||||||
|
|
||||||
async fn send_message(
|
async fn send_message(
|
||||||
&mut self,
|
&mut self,
|
||||||
outgoing_message: OutgoingMessage,
|
outgoing_message: &OutgoingMessage,
|
||||||
) -> Result<Message, Self::Error> {
|
) -> Result<Message, Self::Error> {
|
||||||
let message = Message::builder()
|
let message = Message::builder()
|
||||||
.guid(Uuid::new_v4().to_string())
|
.guid(Uuid::new_v4().to_string())
|
||||||
.text(outgoing_message.text)
|
.text(outgoing_message.text.clone())
|
||||||
.date(OffsetDateTime::now_utc())
|
.date(OffsetDateTime::now_utc())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
self.messages.entry(outgoing_message.conversation_id).or_insert(vec![]).push(message.clone());
|
self.messages.entry(outgoing_message.conversation_id.clone()).or_insert(vec![]).push(message.clone());
|
||||||
Ok(message)
|
Ok(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ kordophone-db = { path = "../kordophone-db" }
|
|||||||
log = "0.4.25"
|
log = "0.4.25"
|
||||||
thiserror = "2.0.12"
|
thiserror = "2.0.12"
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
tokio-condvar = "0.3.0"
|
||||||
|
uuid = "1.16.0"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
dbus-codegen = "0.10.0"
|
dbus-codegen = "0.10.0"
|
||||||
|
|||||||
@@ -58,6 +58,15 @@
|
|||||||
<arg type="aa{sv}" direction="out" name="messages"/>
|
<arg type="aa{sv}" direction="out" name="messages"/>
|
||||||
</method>
|
</method>
|
||||||
|
|
||||||
|
<method name="SendMessage">
|
||||||
|
<arg type="s" name="conversation_id" direction="in"/>
|
||||||
|
<arg type="s" name="text" direction="in"/>
|
||||||
|
<arg type="s" name="outgoing_message_id" direction="out"/>
|
||||||
|
|
||||||
|
<annotation name="org.freedesktop.DBus.DocString"
|
||||||
|
value="Sends a message to the server. Returns the outgoing message ID."/>
|
||||||
|
</method>
|
||||||
|
|
||||||
<signal name="MessagesUpdated">
|
<signal name="MessagesUpdated">
|
||||||
<arg type="s" name="conversation_id" direction="in"/>
|
<arg type="s" name="conversation_id" direction="in"/>
|
||||||
<annotation name="org.freedesktop.DBus.DocString"
|
<annotation name="org.freedesktop.DBus.DocString"
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
use tokio::sync::oneshot;
|
use tokio::sync::oneshot;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
use kordophone_db::models::{Conversation, Message};
|
use kordophone_db::models::{Conversation, Message};
|
||||||
use crate::daemon::settings::Settings;
|
use crate::daemon::settings::Settings;
|
||||||
|
|
||||||
@@ -33,6 +35,13 @@ pub enum Event {
|
|||||||
/// - last_message_id: (optional) The ID of the last message to get. If None, all messages are returned.
|
/// - last_message_id: (optional) The ID of the last message to get. If None, all messages are returned.
|
||||||
GetMessages(String, Option<String>, Reply<Vec<Message>>),
|
GetMessages(String, Option<String>, Reply<Vec<Message>>),
|
||||||
|
|
||||||
|
/// Enqueues a message to be sent to the server.
|
||||||
|
/// Parameters:
|
||||||
|
/// - conversation_id: The ID of the conversation to send the message to.
|
||||||
|
/// - text: The text of the message to send.
|
||||||
|
/// - reply: The outgoing message ID (not the server-assigned message ID).
|
||||||
|
SendMessage(String, String, Reply<Uuid>),
|
||||||
|
|
||||||
/// Delete all conversations from the database.
|
/// Delete all conversations from the database.
|
||||||
DeleteAllConversations(Reply<()>),
|
DeleteAllConversations(Reply<()>),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ use thiserror::Error;
|
|||||||
use tokio::sync::mpsc::{Sender, Receiver};
|
use tokio::sync::mpsc::{Sender, Receiver};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
use kordophone_db::{
|
use kordophone_db::{
|
||||||
database::{Database, DatabaseAccess},
|
database::{Database, DatabaseAccess},
|
||||||
@@ -24,6 +25,7 @@ use kordophone_db::{
|
|||||||
|
|
||||||
use kordophone::api::APIInterface;
|
use kordophone::api::APIInterface;
|
||||||
use kordophone::api::http_client::HTTPAPIClient;
|
use kordophone::api::http_client::HTTPAPIClient;
|
||||||
|
use kordophone::model::outgoing_message::OutgoingMessage;
|
||||||
|
|
||||||
mod update_monitor;
|
mod update_monitor;
|
||||||
use update_monitor::UpdateMonitor;
|
use update_monitor::UpdateMonitor;
|
||||||
@@ -31,6 +33,10 @@ use update_monitor::UpdateMonitor;
|
|||||||
mod auth_store;
|
mod auth_store;
|
||||||
use auth_store::DatabaseAuthenticationStore;
|
use auth_store::DatabaseAuthenticationStore;
|
||||||
|
|
||||||
|
mod post_office;
|
||||||
|
use post_office::PostOffice;
|
||||||
|
use post_office::Event as PostOfficeEvent;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum DaemonError {
|
pub enum DaemonError {
|
||||||
#[error("Client Not Configured")]
|
#[error("Client Not Configured")]
|
||||||
@@ -52,6 +58,9 @@ pub struct Daemon {
|
|||||||
signal_receiver: Option<Receiver<Signal>>,
|
signal_receiver: Option<Receiver<Signal>>,
|
||||||
signal_sender: Sender<Signal>,
|
signal_sender: Sender<Signal>,
|
||||||
|
|
||||||
|
post_office_sink: Sender<PostOfficeEvent>,
|
||||||
|
post_office_source: Option<Receiver<PostOfficeEvent>>,
|
||||||
|
|
||||||
version: String,
|
version: String,
|
||||||
database: Arc<Mutex<Database>>,
|
database: Arc<Mutex<Database>>,
|
||||||
runtime: tokio::runtime::Runtime,
|
runtime: tokio::runtime::Runtime,
|
||||||
@@ -69,6 +78,7 @@ impl Daemon {
|
|||||||
// Create event channels
|
// Create event channels
|
||||||
let (event_sender, event_receiver) = tokio::sync::mpsc::channel(100);
|
let (event_sender, event_receiver) = tokio::sync::mpsc::channel(100);
|
||||||
let (signal_sender, signal_receiver) = tokio::sync::mpsc::channel(100);
|
let (signal_sender, signal_receiver) = tokio::sync::mpsc::channel(100);
|
||||||
|
let (post_office_sink, post_office_source) = tokio::sync::mpsc::channel(100);
|
||||||
// Create background task runtime
|
// Create background task runtime
|
||||||
let runtime = tokio::runtime::Builder::new_multi_thread()
|
let runtime = tokio::runtime::Builder::new_multi_thread()
|
||||||
.enable_all()
|
.enable_all()
|
||||||
@@ -84,6 +94,8 @@ impl Daemon {
|
|||||||
event_sender,
|
event_sender,
|
||||||
signal_receiver: Some(signal_receiver),
|
signal_receiver: Some(signal_receiver),
|
||||||
signal_sender,
|
signal_sender,
|
||||||
|
post_office_sink,
|
||||||
|
post_office_source: Some(post_office_source),
|
||||||
runtime
|
runtime
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -92,12 +104,23 @@ impl Daemon {
|
|||||||
log::info!("Starting daemon version {}", self.version);
|
log::info!("Starting daemon version {}", self.version);
|
||||||
log::debug!("Debug logging enabled.");
|
log::debug!("Debug logging enabled.");
|
||||||
|
|
||||||
|
// Update monitor
|
||||||
let mut update_monitor = UpdateMonitor::new(self.database.clone(), self.event_sender.clone());
|
let mut update_monitor = UpdateMonitor::new(self.database.clone(), self.event_sender.clone());
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
update_monitor.run().await; // should run indefinitely
|
update_monitor.run().await; // should run indefinitely
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Post office
|
||||||
|
{
|
||||||
|
let mut database = self.database.clone();
|
||||||
|
let event_sender = self.event_sender.clone();
|
||||||
|
let post_office_source = self.post_office_source.take().unwrap();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let mut post_office = PostOffice::new(post_office_source, event_sender, async move || Self::get_client_impl(&mut database).await );
|
||||||
|
post_office.run().await;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
while let Some(event) = self.event_receiver.recv().await {
|
while let Some(event) = self.event_receiver.recv().await {
|
||||||
log::debug!(target: target::EVENT, "Received event: {:?}", event);
|
log::debug!(target: target::EVENT, "Received event: {:?}", event);
|
||||||
self.handle_event(event).await;
|
self.handle_event(event).await;
|
||||||
@@ -188,6 +211,11 @@ impl Daemon {
|
|||||||
|
|
||||||
reply.send(()).unwrap();
|
reply.send(()).unwrap();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Event::SendMessage(conversation_id, text, reply) => {
|
||||||
|
let uuid = self.enqueue_outgoing_message(text, conversation_id).await;
|
||||||
|
reply.send(uuid).unwrap();
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,6 +232,18 @@ impl Daemon {
|
|||||||
self.database.lock().await.with_repository(|r| r.get_messages_for_conversation(&conversation_id).unwrap()).await
|
self.database.lock().await.with_repository(|r| r.get_messages_for_conversation(&conversation_id).unwrap()).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn enqueue_outgoing_message(&mut self, text: String, conversation_id: String) -> Uuid {
|
||||||
|
let outgoing_message = OutgoingMessage::builder()
|
||||||
|
.text(text)
|
||||||
|
.conversation_id(conversation_id)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let guid = outgoing_message.guid.clone();
|
||||||
|
self.post_office_sink.send(PostOfficeEvent::EnqueueOutgoingMessage(outgoing_message)).await.unwrap();
|
||||||
|
|
||||||
|
guid
|
||||||
|
}
|
||||||
|
|
||||||
async fn sync_conversation_list(database: &mut Arc<Mutex<Database>>, signal_sender: &Sender<Signal>) -> Result<()> {
|
async fn sync_conversation_list(database: &mut Arc<Mutex<Database>>, signal_sender: &Sender<Signal>) -> Result<()> {
|
||||||
log::info!(target: target::SYNC, "Starting list conversation sync");
|
log::info!(target: target::SYNC, "Starting list conversation sync");
|
||||||
|
|
||||||
|
|||||||
115
kordophoned/src/daemon/post_office.rs
Normal file
115
kordophoned/src/daemon/post_office.rs
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use tokio::sync::mpsc::{Sender, Receiver};
|
||||||
|
use tokio::sync::{Mutex, MutexGuard};
|
||||||
|
use tokio_condvar::Condvar;
|
||||||
|
|
||||||
|
use crate::daemon::events::Event as DaemonEvent;
|
||||||
|
use kordophone::model::outgoing_message::OutgoingMessage;
|
||||||
|
use kordophone::api::APIInterface;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
mod target {
|
||||||
|
pub static POST_OFFICE: &str = "post_office";
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Event {
|
||||||
|
EnqueueOutgoingMessage(OutgoingMessage),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PostOffice<C: APIInterface, F: AsyncFnMut() -> Result<C>> {
|
||||||
|
event_source: Receiver<Event>,
|
||||||
|
event_sink: Sender<DaemonEvent>,
|
||||||
|
make_client: F,
|
||||||
|
message_queue: Mutex<VecDeque<OutgoingMessage>>,
|
||||||
|
message_available: Condvar,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C: APIInterface, F: AsyncFnMut() -> Result<C>> PostOffice<C, F> {
|
||||||
|
pub fn new(event_source: Receiver<Event>, event_sink: Sender<DaemonEvent>, make_client: F) -> Self {
|
||||||
|
Self {
|
||||||
|
event_source,
|
||||||
|
event_sink,
|
||||||
|
make_client,
|
||||||
|
message_queue: Mutex::new(VecDeque::new()),
|
||||||
|
message_available: Condvar::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn queue_message(&mut self, message: &OutgoingMessage) {
|
||||||
|
self.message_queue.lock().await.push_back(message.clone());
|
||||||
|
self.message_available.notify_one();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run(&mut self) {
|
||||||
|
log::info!(target: target::POST_OFFICE, "Starting post office");
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let mut retry_messages = Vec::new();
|
||||||
|
|
||||||
|
log::debug!(target: target::POST_OFFICE, "Waiting for event");
|
||||||
|
|
||||||
|
tokio::select! {
|
||||||
|
// Incoming events
|
||||||
|
Some(event) = self.event_source.recv() => {
|
||||||
|
match event {
|
||||||
|
Event::EnqueueOutgoingMessage(message) => {
|
||||||
|
log::debug!(target: target::POST_OFFICE, "Received enqueue outgoing message event");
|
||||||
|
self.message_queue.lock().await.push_back(message);
|
||||||
|
self.message_available.notify_one();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message queue
|
||||||
|
mut lock = self.message_available.wait(self.message_queue.lock().await) => {
|
||||||
|
log::debug!(target: target::POST_OFFICE, "Message available in queue");
|
||||||
|
retry_messages = Self::try_send_message_impl(&mut lock, &mut self.make_client).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !retry_messages.is_empty() {
|
||||||
|
log::debug!(target: target::POST_OFFICE, "Queueing {} messages for retry", retry_messages.len());
|
||||||
|
for message in retry_messages {
|
||||||
|
self.queue_message(&message).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn try_send_message_impl(message_queue: &mut MutexGuard<'_, VecDeque<OutgoingMessage>>, make_client: &mut F) -> Vec<OutgoingMessage> {
|
||||||
|
log::debug!(target: target::POST_OFFICE, "Trying to send enqueued messages");
|
||||||
|
|
||||||
|
let mut retry_messages = Vec::new();
|
||||||
|
while let Some(message) = message_queue.pop_front() {
|
||||||
|
match (make_client)().await {
|
||||||
|
Ok(mut client) => {
|
||||||
|
log::debug!(target: target::POST_OFFICE, "Obtained client, sending message.");
|
||||||
|
match client.send_message(&message).await {
|
||||||
|
Ok(message) => {
|
||||||
|
log::info!(target: target::POST_OFFICE, "Message sent successfully: {}", message.guid);
|
||||||
|
// TODO: Notify the daemon via the event sink.
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!(target: target::POST_OFFICE, "Error sending message: {:?}", e);
|
||||||
|
log::warn!(target: target::POST_OFFICE, "Retrying in 5 seconds");
|
||||||
|
tokio::time::sleep(Duration::from_secs(5)).await;
|
||||||
|
retry_messages.push(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(e) => {
|
||||||
|
log::error!(target: target::POST_OFFICE, "Error creating client: {:?}", e);
|
||||||
|
log::warn!(target: target::POST_OFFICE, "Retrying in 5 seconds");
|
||||||
|
tokio::time::sleep(Duration::from_secs(5)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
retry_messages
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -102,6 +102,11 @@ impl DbusRepository for ServerImpl {
|
|||||||
fn delete_all_conversations(&mut self) -> Result<(), dbus::MethodErr> {
|
fn delete_all_conversations(&mut self) -> Result<(), dbus::MethodErr> {
|
||||||
self.send_event_sync(Event::DeleteAllConversations)
|
self.send_event_sync(Event::DeleteAllConversations)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn send_message(&mut self, conversation_id: String, text: String) -> Result<String, dbus::MethodErr> {
|
||||||
|
self.send_event_sync(|r| Event::SendMessage(conversation_id, text, r))
|
||||||
|
.map(|uuid| uuid.to_string())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DbusSettings for ServerImpl {
|
impl DbusSettings for ServerImpl {
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ impl ClientCli {
|
|||||||
.text(message)
|
.text(message)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let message = self.api.send_message(outgoing_message).await?;
|
let message = self.api.send_message(&outgoing_message).await?;
|
||||||
println!("Message sent: {}", message.guid);
|
println!("Message sent: {}", message.guid);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,12 @@ pub enum Commands {
|
|||||||
|
|
||||||
/// Deletes all conversations.
|
/// Deletes all conversations.
|
||||||
DeleteAllConversations,
|
DeleteAllConversations,
|
||||||
|
|
||||||
|
/// Enqueues an outgoing message to be sent to a conversation.
|
||||||
|
SendMessage {
|
||||||
|
conversation_id: String,
|
||||||
|
text: String,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
#[derive(Subcommand)]
|
||||||
@@ -83,6 +89,7 @@ impl Commands {
|
|||||||
Commands::Signals => client.wait_for_signals().await,
|
Commands::Signals => client.wait_for_signals().await,
|
||||||
Commands::Messages { conversation_id, last_message_id } => client.print_messages(conversation_id, last_message_id).await,
|
Commands::Messages { conversation_id, last_message_id } => client.print_messages(conversation_id, last_message_id).await,
|
||||||
Commands::DeleteAllConversations => client.delete_all_conversations().await,
|
Commands::DeleteAllConversations => client.delete_all_conversations().await,
|
||||||
|
Commands::SendMessage { conversation_id, text } => client.enqueue_outgoing_message(conversation_id, text).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -145,6 +152,12 @@ impl DaemonCli {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn enqueue_outgoing_message(&mut self, conversation_id: String, text: String) -> Result<()> {
|
||||||
|
let outgoing_message_id = KordophoneRepository::send_message(&self.proxy(), &conversation_id, &text)?;
|
||||||
|
println!("Outgoing message ID: {}", outgoing_message_id);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn wait_for_signals(&mut self) -> Result<()> {
|
pub async fn wait_for_signals(&mut self) -> Result<()> {
|
||||||
use dbus::Message;
|
use dbus::Message;
|
||||||
mod dbus_signals {
|
mod dbus_signals {
|
||||||
|
|||||||
Reference in New Issue
Block a user