first attempt: notification code is in dbus::agent
This commit is contained in:
840
core/Cargo.lock
generated
840
core/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -307,8 +307,8 @@ impl<'a> Repository<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_all_messages(&mut self) -> Result<()> {
|
pub fn delete_all_messages(&mut self) -> Result<()> {
|
||||||
use crate::schema::messages::dsl as messages_dsl;
|
|
||||||
use crate::schema::message_aliases::dsl as aliases_dsl;
|
use crate::schema::message_aliases::dsl as aliases_dsl;
|
||||||
|
use crate::schema::messages::dsl as messages_dsl;
|
||||||
|
|
||||||
diesel::delete(messages_dsl::messages).execute(self.connection)?;
|
diesel::delete(messages_dsl::messages).execute(self.connection)?;
|
||||||
diesel::delete(aliases_dsl::message_aliases).execute(self.connection)?;
|
diesel::delete(aliases_dsl::message_aliases).execute(self.connection)?;
|
||||||
|
|||||||
@@ -394,8 +394,7 @@ impl<K: AuthenticationStore + Send + Sync> APIInterface for HTTPAPIClient<K> {
|
|||||||
None => "updates".to_string(),
|
None => "updates".to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let uri = self
|
let uri = self.uri_for_endpoint(&endpoint, Some(self.websocket_scheme()))?;
|
||||||
.uri_for_endpoint(&endpoint, Some(self.websocket_scheme()))?;
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
log::debug!("Connecting to websocket: {:?}", uri);
|
log::debug!("Connecting to websocket: {:?}", uri);
|
||||||
@@ -430,14 +429,16 @@ impl<K: AuthenticationStore + Send + Sync> APIInterface for HTTPAPIClient<K> {
|
|||||||
match connect_async(request).await.map_err(Error::from) {
|
match connect_async(request).await.map_err(Error::from) {
|
||||||
Ok((socket, response)) => {
|
Ok((socket, response)) => {
|
||||||
log::debug!("Websocket connected: {:?}", response.status());
|
log::debug!("Websocket connected: {:?}", response.status());
|
||||||
break Ok(WebsocketEventSocket::new(socket))
|
break Ok(WebsocketEventSocket::new(socket));
|
||||||
}
|
}
|
||||||
Err(e) => match &e {
|
Err(e) => match &e {
|
||||||
Error::ClientError(ce) => match ce.as_str() {
|
Error::ClientError(ce) => match ce.as_str() {
|
||||||
"HTTP error: 401 Unauthorized" | "Unauthorized" => {
|
"HTTP error: 401 Unauthorized" | "Unauthorized" => {
|
||||||
// Try to authenticate
|
// Try to authenticate
|
||||||
if let Some(credentials) = &self.auth_store.get_credentials().await {
|
if let Some(credentials) = &self.auth_store.get_credentials().await {
|
||||||
log::warn!("Websocket connection failed, attempting to authenticate");
|
log::warn!(
|
||||||
|
"Websocket connection failed, attempting to authenticate"
|
||||||
|
);
|
||||||
let new_token = self.authenticate(credentials.clone()).await?;
|
let new_token = self.authenticate(credentials.clone()).await?;
|
||||||
self.auth_store.set_token(new_token.to_string()).await;
|
self.auth_store.set_token(new_token.to_string()).await;
|
||||||
|
|
||||||
@@ -469,16 +470,16 @@ impl<K: AuthenticationStore + Send + Sync> HTTPAPIClient<K> {
|
|||||||
let https = HttpsConnector::new();
|
let https = HttpsConnector::new();
|
||||||
let client = Client::builder().build::<_, Body>(https);
|
let client = Client::builder().build::<_, Body>(https);
|
||||||
|
|
||||||
HTTPAPIClient { base_url, auth_store, client }
|
HTTPAPIClient {
|
||||||
|
base_url,
|
||||||
|
auth_store,
|
||||||
|
client,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn uri_for_endpoint(&self, endpoint: &str, scheme: Option<&str>) -> Result<Uri, Error> {
|
fn uri_for_endpoint(&self, endpoint: &str, scheme: Option<&str>) -> Result<Uri, Error> {
|
||||||
let mut parts = self.base_url.clone().into_parts();
|
let mut parts = self.base_url.clone().into_parts();
|
||||||
let root_path: PathBuf = parts
|
let root_path: PathBuf = parts.path_and_query.ok_or(Error::URLError)?.path().into();
|
||||||
.path_and_query
|
|
||||||
.ok_or(Error::URLError)?
|
|
||||||
.path()
|
|
||||||
.into();
|
|
||||||
|
|
||||||
let path = root_path.join(endpoint);
|
let path = root_path.join(endpoint);
|
||||||
let path_str = path.to_str().ok_or(Error::URLError)?;
|
let path_str = path.to_str().ok_or(Error::URLError)?;
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ tokio-condvar = "0.3.0"
|
|||||||
uuid = "1.16.0"
|
uuid = "1.16.0"
|
||||||
once_cell = "1.19.0"
|
once_cell = "1.19.0"
|
||||||
mime_guess = "2.0"
|
mime_guess = "2.0"
|
||||||
|
notify = { package = "notify-rust", version = "4.10.0" }
|
||||||
|
|
||||||
# D-Bus dependencies only on Linux
|
# D-Bus dependencies only on Linux
|
||||||
[target.'cfg(target_os = "linux")'.dependencies]
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
|
|||||||
@@ -103,6 +103,13 @@
|
|||||||
"/>
|
"/>
|
||||||
</method>
|
</method>
|
||||||
|
|
||||||
|
<method name="TestNotification">
|
||||||
|
<arg type="s" name="summary" direction="in"/>
|
||||||
|
<arg type="s" name="body" direction="in"/>
|
||||||
|
<annotation name="org.freedesktop.DBus.DocString"
|
||||||
|
value="Displays a test desktop notification with the provided summary and body."/>
|
||||||
|
</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"
|
||||||
|
|||||||
@@ -41,6 +41,12 @@ pub enum Event {
|
|||||||
/// - offset: The offset into the conversation list to start returning conversations from.
|
/// - offset: The offset into the conversation list to start returning conversations from.
|
||||||
GetAllConversations(i32, i32, Reply<Vec<Conversation>>),
|
GetAllConversations(i32, i32, Reply<Vec<Conversation>>),
|
||||||
|
|
||||||
|
/// Returns a conversation by its ID.
|
||||||
|
GetConversation(String, Reply<Option<Conversation>>),
|
||||||
|
|
||||||
|
/// Returns the most recent message for a conversation.
|
||||||
|
GetLastMessage(String, Reply<Option<Message>>),
|
||||||
|
|
||||||
/// Returns all known settings from the database.
|
/// Returns all known settings from the database.
|
||||||
GetAllSettings(Reply<Settings>),
|
GetAllSettings(Reply<Settings>),
|
||||||
|
|
||||||
|
|||||||
@@ -271,6 +271,16 @@ impl Daemon {
|
|||||||
reply.send(conversations).unwrap();
|
reply.send(conversations).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Event::GetConversation(conversation_id, reply) => {
|
||||||
|
let conversation = self.get_conversation(conversation_id).await;
|
||||||
|
reply.send(conversation).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
Event::GetLastMessage(conversation_id, reply) => {
|
||||||
|
let message = self.get_last_message(conversation_id).await;
|
||||||
|
reply.send(message).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
Event::GetAllSettings(reply) => {
|
Event::GetAllSettings(reply) => {
|
||||||
let settings = self.get_settings().await.unwrap_or_else(|e| {
|
let settings = self.get_settings().await.unwrap_or_else(|e| {
|
||||||
log::error!(target: target::SETTINGS, "Failed to get settings: {:#?}", e);
|
log::error!(target: target::SETTINGS, "Failed to get settings: {:#?}", e);
|
||||||
@@ -433,6 +443,14 @@ impl Daemon {
|
|||||||
self.signal_receiver.take().unwrap()
|
self.signal_receiver.take().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_conversation(&mut self, conversation_id: String) -> Option<Conversation> {
|
||||||
|
self.database
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.with_repository(|r| r.get_conversation_by_guid(&conversation_id).unwrap())
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
async fn get_conversations_limit_offset(
|
async fn get_conversations_limit_offset(
|
||||||
&mut self,
|
&mut self,
|
||||||
limit: i32,
|
limit: i32,
|
||||||
@@ -445,6 +463,18 @@ impl Daemon {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_last_message(&mut self, conversation_id: String) -> Option<Message> {
|
||||||
|
self.database
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.with_repository(|r| {
|
||||||
|
r.get_last_message_for_conversation(&conversation_id)
|
||||||
|
.unwrap()
|
||||||
|
.map(|message| message.into())
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
async fn get_messages(
|
async fn get_messages(
|
||||||
&mut self,
|
&mut self,
|
||||||
conversation_id: String,
|
conversation_id: String,
|
||||||
@@ -471,9 +501,8 @@ impl Daemon {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
// Convert DB messages to daemon model, substituting local_id when an alias exists.
|
// Convert DB messages to daemon model, substituting local_id when an alias exists.
|
||||||
let mut result: Vec<Message> = Vec::with_capacity(
|
let mut result: Vec<Message> =
|
||||||
db_messages.len() + outgoing_messages.len(),
|
Vec::with_capacity(db_messages.len() + outgoing_messages.len());
|
||||||
);
|
|
||||||
for m in db_messages.into_iter() {
|
for m in db_messages.into_iter() {
|
||||||
let server_id = m.id.clone();
|
let server_id = m.id.clone();
|
||||||
let mut dm: Message = m.into();
|
let mut dm: Message = m.into();
|
||||||
|
|||||||
@@ -25,8 +25,7 @@ impl Attachment {
|
|||||||
// Prefer common, user-friendly extensions over obscure ones
|
// Prefer common, user-friendly extensions over obscure ones
|
||||||
match normalized {
|
match normalized {
|
||||||
"image/jpeg" | "image/pjpeg" => Some("jpg"),
|
"image/jpeg" | "image/pjpeg" => Some("jpg"),
|
||||||
_ => mime_guess::get_mime_extensions_str(normalized)
|
_ => mime_guess::get_mime_extensions_str(normalized).and_then(|list| {
|
||||||
.and_then(|list| {
|
|
||||||
// If jpg is one of the candidates, prefer it
|
// If jpg is one of the candidates, prefer it
|
||||||
if list.iter().any(|e| *e == "jpg") {
|
if list.iter().any(|e| *e == "jpg") {
|
||||||
Some("jpg")
|
Some("jpg")
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use dbus::arg;
|
use dbus::arg;
|
||||||
use dbus_tree::MethodErr;
|
use dbus_tree::MethodErr;
|
||||||
use std::sync::Arc;
|
use notify::Notification;
|
||||||
|
use std::sync::{Arc, Mutex as StdMutex};
|
||||||
use std::{future::Future, thread};
|
use std::{future::Future, thread};
|
||||||
use tokio::sync::{mpsc, oneshot, Mutex};
|
use tokio::sync::{mpsc, oneshot, Mutex};
|
||||||
|
|
||||||
@@ -9,10 +10,11 @@ use kordophoned::daemon::{
|
|||||||
events::{Event, Reply},
|
events::{Event, Reply},
|
||||||
settings::Settings,
|
settings::Settings,
|
||||||
signals::Signal,
|
signals::Signal,
|
||||||
DaemonResult,
|
DaemonResult, Message,
|
||||||
};
|
};
|
||||||
|
|
||||||
use kordophone_db::models::participant::Participant;
|
use kordophone_db::models::participant::Participant;
|
||||||
|
use kordophone_db::models::Conversation;
|
||||||
|
|
||||||
use crate::dbus::endpoint::DbusRegistry;
|
use crate::dbus::endpoint::DbusRegistry;
|
||||||
use crate::dbus::interface;
|
use crate::dbus::interface;
|
||||||
@@ -23,7 +25,7 @@ use dbus_tokio::connection;
|
|||||||
pub struct DBusAgent {
|
pub struct DBusAgent {
|
||||||
event_sink: mpsc::Sender<Event>,
|
event_sink: mpsc::Sender<Event>,
|
||||||
signal_receiver: Arc<Mutex<Option<mpsc::Receiver<Signal>>>>,
|
signal_receiver: Arc<Mutex<Option<mpsc::Receiver<Signal>>>>,
|
||||||
contact_resolver: ContactResolver<DefaultContactResolverBackend>,
|
contact_resolver: Arc<StdMutex<ContactResolver<DefaultContactResolverBackend>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DBusAgent {
|
impl DBusAgent {
|
||||||
@@ -31,7 +33,9 @@ impl DBusAgent {
|
|||||||
Self {
|
Self {
|
||||||
event_sink,
|
event_sink,
|
||||||
signal_receiver: Arc::new(Mutex::new(Some(signal_receiver))),
|
signal_receiver: Arc::new(Mutex::new(Some(signal_receiver))),
|
||||||
contact_resolver: ContactResolver::new(DefaultContactResolverBackend::default()),
|
contact_resolver: Arc::new(StdMutex::new(ContactResolver::new(
|
||||||
|
DefaultContactResolverBackend::default(),
|
||||||
|
))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,6 +72,7 @@ impl DBusAgent {
|
|||||||
{
|
{
|
||||||
let registry = dbus_registry.clone();
|
let registry = dbus_registry.clone();
|
||||||
let receiver_arc = self.signal_receiver.clone();
|
let receiver_arc = self.signal_receiver.clone();
|
||||||
|
let agent_clone = self.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let mut receiver = receiver_arc
|
let mut receiver = receiver_arc
|
||||||
.lock()
|
.lock()
|
||||||
@@ -94,6 +99,7 @@ impl DBusAgent {
|
|||||||
"Sending signal: MessagesUpdated for conversation {}",
|
"Sending signal: MessagesUpdated for conversation {}",
|
||||||
conversation_id
|
conversation_id
|
||||||
);
|
);
|
||||||
|
let conversation_id_for_notification = conversation_id.clone();
|
||||||
registry
|
registry
|
||||||
.send_signal(
|
.send_signal(
|
||||||
interface::OBJECT_PATH,
|
interface::OBJECT_PATH,
|
||||||
@@ -103,6 +109,10 @@ impl DBusAgent {
|
|||||||
log::error!("Failed to send signal");
|
log::error!("Failed to send signal");
|
||||||
0
|
0
|
||||||
});
|
});
|
||||||
|
|
||||||
|
agent_clone
|
||||||
|
.maybe_notify_on_messages_updated(&conversation_id_for_notification)
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
Signal::AttachmentDownloaded(attachment_id) => {
|
Signal::AttachmentDownloaded(attachment_id) => {
|
||||||
log::debug!(
|
log::debug!(
|
||||||
@@ -181,7 +191,7 @@ impl DBusAgent {
|
|||||||
.map_err(|e| MethodErr::failed(&format!("Daemon error: {}", e)))
|
.map_err(|e| MethodErr::failed(&format!("Daemon error: {}", e)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_participant_display_name(&mut self, participant: &Participant) -> String {
|
fn resolve_participant_display_name(&self, participant: &Participant) -> String {
|
||||||
match participant {
|
match participant {
|
||||||
// Me (we should use a special string here...)
|
// Me (we should use a special string here...)
|
||||||
Participant::Me => "(Me)".to_string(),
|
Participant::Me => "(Me)".to_string(),
|
||||||
@@ -191,10 +201,15 @@ impl DBusAgent {
|
|||||||
handle,
|
handle,
|
||||||
contact_id: Some(contact_id),
|
contact_id: Some(contact_id),
|
||||||
..
|
..
|
||||||
} => self
|
} => {
|
||||||
.contact_resolver
|
if let Ok(mut resolver) = self.contact_resolver.lock() {
|
||||||
|
resolver
|
||||||
.get_contact_display_name(contact_id)
|
.get_contact_display_name(contact_id)
|
||||||
.unwrap_or_else(|| handle.clone()),
|
.unwrap_or_else(|| handle.clone())
|
||||||
|
} else {
|
||||||
|
handle.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Remote participant without a resolved contact_id
|
// Remote participant without a resolved contact_id
|
||||||
Participant::Remote { handle, .. } => handle.clone(),
|
Participant::Remote { handle, .. } => handle.clone(),
|
||||||
@@ -202,6 +217,113 @@ impl DBusAgent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl DBusAgent {
|
||||||
|
fn conversation_display_name(&self, conversation: &Conversation) -> String {
|
||||||
|
if let Some(display_name) = &conversation.display_name {
|
||||||
|
if !display_name.trim().is_empty() {
|
||||||
|
return display_name.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let names: Vec<String> = conversation
|
||||||
|
.participants
|
||||||
|
.iter()
|
||||||
|
.filter(|participant| !matches!(participant, Participant::Me))
|
||||||
|
.map(|participant| self.resolve_participant_display_name(participant))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if names.is_empty() {
|
||||||
|
"Kordophone".to_string()
|
||||||
|
} else {
|
||||||
|
names.join(", ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn prepare_incoming_message_notification(
|
||||||
|
&self,
|
||||||
|
conversation_id: &str,
|
||||||
|
) -> DaemonResult<Option<(String, String)>> {
|
||||||
|
let conversation = match self
|
||||||
|
.send_event(|reply| Event::GetConversation(conversation_id.to_string(), reply))
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
Some(conv) => conv,
|
||||||
|
None => return Ok(None),
|
||||||
|
};
|
||||||
|
|
||||||
|
if conversation.unread_count == 0 {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
let last_message: Option<Message> = self
|
||||||
|
.send_event(|reply| Event::GetLastMessage(conversation_id.to_string(), reply))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let last_message = match last_message {
|
||||||
|
Some(message) => message,
|
||||||
|
None => return Ok(None),
|
||||||
|
};
|
||||||
|
|
||||||
|
let sender_participant: Participant = Participant::from(last_message.sender.clone());
|
||||||
|
if matches!(sender_participant, Participant::Me) {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
let summary = self.conversation_display_name(&conversation);
|
||||||
|
let sender_display_name = self.resolve_participant_display_name(&sender_participant);
|
||||||
|
|
||||||
|
let mut message_text = last_message.text.replace('\u{FFFC}', "");
|
||||||
|
if message_text.trim().is_empty() {
|
||||||
|
if !last_message.attachments.is_empty() {
|
||||||
|
message_text = "Sent an attachment".to_string();
|
||||||
|
} else {
|
||||||
|
message_text = "Sent a message".to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let body = if sender_display_name.is_empty() {
|
||||||
|
message_text
|
||||||
|
} else {
|
||||||
|
format!("{}: {}", sender_display_name, message_text)
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Some((summary, body)))
|
||||||
|
}
|
||||||
|
fn show_notification(&self, summary: &str, body: &str) -> Result<(), notify::error::Error> {
|
||||||
|
Notification::new()
|
||||||
|
.appname("Kordophone")
|
||||||
|
.summary(summary)
|
||||||
|
.body(body)
|
||||||
|
.show()
|
||||||
|
.map(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn maybe_notify_on_messages_updated(&self, conversation_id: &str) {
|
||||||
|
match self
|
||||||
|
.prepare_incoming_message_notification(conversation_id)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(Some((summary, body))) => {
|
||||||
|
if let Err(error) = self.show_notification(&summary, &body) {
|
||||||
|
log::warn!(
|
||||||
|
"Failed to display notification for conversation {}: {}",
|
||||||
|
conversation_id,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None) => {}
|
||||||
|
Err(error) => {
|
||||||
|
log::warn!(
|
||||||
|
"Unable to prepare notification for conversation {}: {}",
|
||||||
|
conversation_id,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// D-Bus repository interface implementation
|
// D-Bus repository interface implementation
|
||||||
//
|
//
|
||||||
@@ -398,6 +520,11 @@ impl DbusRepository for DBusAgent {
|
|||||||
.map(|uuid| uuid.to_string())
|
.map(|uuid| uuid.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn test_notification(&mut self, summary: String, body: String) -> Result<(), MethodErr> {
|
||||||
|
self.show_notification(&summary, &body)
|
||||||
|
.map_err(|e| MethodErr::failed(&format!("Failed to display notification: {}", e)))
|
||||||
|
}
|
||||||
|
|
||||||
fn get_attachment_info(
|
fn get_attachment_info(
|
||||||
&mut self,
|
&mut self,
|
||||||
attachment_id: String,
|
attachment_id: String,
|
||||||
|
|||||||
@@ -15,10 +15,16 @@ pub struct DispatchResult {
|
|||||||
|
|
||||||
impl DispatchResult {
|
impl DispatchResult {
|
||||||
pub fn new(message: Message) -> Self {
|
pub fn new(message: Message) -> Self {
|
||||||
Self { message, cleanup: None }
|
Self {
|
||||||
|
message,
|
||||||
|
cleanup: None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_cleanup<T: Any + Send + 'static>(message: Message, cleanup: T) -> Self {
|
pub fn with_cleanup<T: Any + Send + 'static>(message: Message, cleanup: T) -> Self {
|
||||||
Self { message, cleanup: Some(Box::new(cleanup)) }
|
Self {
|
||||||
|
message,
|
||||||
|
cleanup: Some(Box::new(cleanup)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,7 +105,12 @@ pub async fn dispatch(
|
|||||||
.and_then(|m| dict_get_str(m, "conversation_id"))
|
.and_then(|m| dict_get_str(m, "conversation_id"))
|
||||||
{
|
{
|
||||||
Some(id) => id,
|
Some(id) => id,
|
||||||
None => return DispatchResult::new(make_error_reply("InvalidRequest", "Missing conversation_id")),
|
None => {
|
||||||
|
return DispatchResult::new(make_error_reply(
|
||||||
|
"InvalidRequest",
|
||||||
|
"Missing conversation_id",
|
||||||
|
))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
match agent
|
match agent
|
||||||
.send_event(|r| Event::SyncConversation(conversation_id, r))
|
.send_event(|r| Event::SyncConversation(conversation_id, r))
|
||||||
@@ -122,7 +127,12 @@ pub async fn dispatch(
|
|||||||
.and_then(|m| dict_get_str(m, "conversation_id"))
|
.and_then(|m| dict_get_str(m, "conversation_id"))
|
||||||
{
|
{
|
||||||
Some(id) => id,
|
Some(id) => id,
|
||||||
None => return DispatchResult::new(make_error_reply("InvalidRequest", "Missing conversation_id")),
|
None => {
|
||||||
|
return DispatchResult::new(make_error_reply(
|
||||||
|
"InvalidRequest",
|
||||||
|
"Missing conversation_id",
|
||||||
|
))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
match agent
|
match agent
|
||||||
.send_event(|r| Event::MarkConversationAsRead(conversation_id, r))
|
.send_event(|r| Event::MarkConversationAsRead(conversation_id, r))
|
||||||
@@ -137,11 +147,21 @@ pub async fn dispatch(
|
|||||||
"GetMessages" => {
|
"GetMessages" => {
|
||||||
let args = match get_dictionary_field(root, "arguments") {
|
let args = match get_dictionary_field(root, "arguments") {
|
||||||
Some(a) => a,
|
Some(a) => a,
|
||||||
None => return DispatchResult::new(make_error_reply("InvalidRequest", "Missing arguments")),
|
None => {
|
||||||
|
return DispatchResult::new(make_error_reply(
|
||||||
|
"InvalidRequest",
|
||||||
|
"Missing arguments",
|
||||||
|
))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let conversation_id = match dict_get_str(args, "conversation_id") {
|
let conversation_id = match dict_get_str(args, "conversation_id") {
|
||||||
Some(id) => id,
|
Some(id) => id,
|
||||||
None => return DispatchResult::new(make_error_reply("InvalidRequest", "Missing conversation_id")),
|
None => {
|
||||||
|
return DispatchResult::new(make_error_reply(
|
||||||
|
"InvalidRequest",
|
||||||
|
"Missing conversation_id",
|
||||||
|
))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let last_message_id = dict_get_str(args, "last_message_id");
|
let last_message_id = dict_get_str(args, "last_message_id");
|
||||||
match agent
|
match agent
|
||||||
@@ -158,11 +178,8 @@ pub async fn dispatch(
|
|||||||
dict_put_str(&mut m, "sender", &msg.sender.display_name());
|
dict_put_str(&mut m, "sender", &msg.sender.display_name());
|
||||||
|
|
||||||
// Include attachment GUIDs for the client to resolve/download
|
// Include attachment GUIDs for the client to resolve/download
|
||||||
let attachment_guids: Vec<String> = msg
|
let attachment_guids: Vec<String> =
|
||||||
.attachments
|
msg.attachments.iter().map(|a| a.guid.clone()).collect();
|
||||||
.iter()
|
|
||||||
.map(|a| a.guid.clone())
|
|
||||||
.collect();
|
|
||||||
m.insert(cstr("attachment_guids"), array_from_strs(attachment_guids));
|
m.insert(cstr("attachment_guids"), array_from_strs(attachment_guids));
|
||||||
|
|
||||||
// Full attachments array with metadata (mirrors DBus fields)
|
// Full attachments array with metadata (mirrors DBus fields)
|
||||||
@@ -193,12 +210,23 @@ pub async fn dispatch(
|
|||||||
if let Some(attribution_info) = &metadata.attribution_info {
|
if let Some(attribution_info) = &metadata.attribution_info {
|
||||||
let mut attribution_map: XpcMap = HashMap::new();
|
let mut attribution_map: XpcMap = HashMap::new();
|
||||||
if let Some(width) = attribution_info.width {
|
if let Some(width) = attribution_info.width {
|
||||||
dict_put_i64_as_str(&mut attribution_map, "width", width as i64);
|
dict_put_i64_as_str(
|
||||||
|
&mut attribution_map,
|
||||||
|
"width",
|
||||||
|
width as i64,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if let Some(height) = attribution_info.height {
|
if let Some(height) = attribution_info.height {
|
||||||
dict_put_i64_as_str(&mut attribution_map, "height", height as i64);
|
dict_put_i64_as_str(
|
||||||
|
&mut attribution_map,
|
||||||
|
"height",
|
||||||
|
height as i64,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
metadata_map.insert(cstr("attribution_info"), Message::Dictionary(attribution_map));
|
metadata_map.insert(
|
||||||
|
cstr("attribution_info"),
|
||||||
|
Message::Dictionary(attribution_map),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if !metadata_map.is_empty() {
|
if !metadata_map.is_empty() {
|
||||||
a.insert(cstr("metadata"), Message::Dictionary(metadata_map));
|
a.insert(cstr("metadata"), Message::Dictionary(metadata_map));
|
||||||
@@ -230,11 +258,21 @@ pub async fn dispatch(
|
|||||||
"SendMessage" => {
|
"SendMessage" => {
|
||||||
let args = match get_dictionary_field(root, "arguments") {
|
let args = match get_dictionary_field(root, "arguments") {
|
||||||
Some(a) => a,
|
Some(a) => a,
|
||||||
None => return DispatchResult::new(make_error_reply("InvalidRequest", "Missing arguments")),
|
None => {
|
||||||
|
return DispatchResult::new(make_error_reply(
|
||||||
|
"InvalidRequest",
|
||||||
|
"Missing arguments",
|
||||||
|
))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let conversation_id = match dict_get_str(args, "conversation_id") {
|
let conversation_id = match dict_get_str(args, "conversation_id") {
|
||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
None => return DispatchResult::new(make_error_reply("InvalidRequest", "Missing conversation_id")),
|
None => {
|
||||||
|
return DispatchResult::new(make_error_reply(
|
||||||
|
"InvalidRequest",
|
||||||
|
"Missing conversation_id",
|
||||||
|
))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let text = dict_get_str(args, "text").unwrap_or_default();
|
let text = dict_get_str(args, "text").unwrap_or_default();
|
||||||
let attachment_guids: Vec<String> = match args.get(&cstr("attachment_guids")) {
|
let attachment_guids: Vec<String> = match args.get(&cstr("attachment_guids")) {
|
||||||
@@ -265,11 +303,21 @@ pub async fn dispatch(
|
|||||||
"GetAttachmentInfo" => {
|
"GetAttachmentInfo" => {
|
||||||
let args = match get_dictionary_field(root, "arguments") {
|
let args = match get_dictionary_field(root, "arguments") {
|
||||||
Some(a) => a,
|
Some(a) => a,
|
||||||
None => return DispatchResult::new(make_error_reply("InvalidRequest", "Missing arguments")),
|
None => {
|
||||||
|
return DispatchResult::new(make_error_reply(
|
||||||
|
"InvalidRequest",
|
||||||
|
"Missing arguments",
|
||||||
|
))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let attachment_id = match dict_get_str(args, "attachment_id") {
|
let attachment_id = match dict_get_str(args, "attachment_id") {
|
||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
None => return DispatchResult::new(make_error_reply("InvalidRequest", "Missing attachment_id")),
|
None => {
|
||||||
|
return DispatchResult::new(make_error_reply(
|
||||||
|
"InvalidRequest",
|
||||||
|
"Missing attachment_id",
|
||||||
|
))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
match agent
|
match agent
|
||||||
.send_event(|r| Event::GetAttachment(attachment_id, r))
|
.send_event(|r| Event::GetAttachment(attachment_id, r))
|
||||||
@@ -308,11 +356,21 @@ pub async fn dispatch(
|
|||||||
"OpenAttachmentFd" => {
|
"OpenAttachmentFd" => {
|
||||||
let args = match get_dictionary_field(root, "arguments") {
|
let args = match get_dictionary_field(root, "arguments") {
|
||||||
Some(a) => a,
|
Some(a) => a,
|
||||||
None => return DispatchResult::new(make_error_reply("InvalidRequest", "Missing arguments")),
|
None => {
|
||||||
|
return DispatchResult::new(make_error_reply(
|
||||||
|
"InvalidRequest",
|
||||||
|
"Missing arguments",
|
||||||
|
))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let attachment_id = match dict_get_str(args, "attachment_id") {
|
let attachment_id = match dict_get_str(args, "attachment_id") {
|
||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
None => return DispatchResult::new(make_error_reply("InvalidRequest", "Missing attachment_id")),
|
None => {
|
||||||
|
return DispatchResult::new(make_error_reply(
|
||||||
|
"InvalidRequest",
|
||||||
|
"Missing attachment_id",
|
||||||
|
))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let preview = dict_get_str(args, "preview")
|
let preview = dict_get_str(args, "preview")
|
||||||
.map(|s| s == "true")
|
.map(|s| s == "true")
|
||||||
@@ -335,9 +393,14 @@ pub async fn dispatch(
|
|||||||
dict_put_str(&mut reply, "type", "OpenAttachmentFdResponse");
|
dict_put_str(&mut reply, "type", "OpenAttachmentFdResponse");
|
||||||
reply.insert(cstr("fd"), Message::Fd(fd));
|
reply.insert(cstr("fd"), Message::Fd(fd));
|
||||||
|
|
||||||
DispatchResult { message: Message::Dictionary(reply), cleanup: Some(Box::new(file)) }
|
DispatchResult {
|
||||||
|
message: Message::Dictionary(reply),
|
||||||
|
cleanup: Some(Box::new(file)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
DispatchResult::new(make_error_reply("OpenFailed", &format!("{}", e)))
|
||||||
}
|
}
|
||||||
Err(e) => DispatchResult::new(make_error_reply("OpenFailed", &format!("{}", e))),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => DispatchResult::new(make_error_reply("DaemonError", &format!("{}", e))),
|
Err(e) => DispatchResult::new(make_error_reply("DaemonError", &format!("{}", e))),
|
||||||
@@ -348,11 +411,21 @@ pub async fn dispatch(
|
|||||||
"DownloadAttachment" => {
|
"DownloadAttachment" => {
|
||||||
let args = match get_dictionary_field(root, "arguments") {
|
let args = match get_dictionary_field(root, "arguments") {
|
||||||
Some(a) => a,
|
Some(a) => a,
|
||||||
None => return DispatchResult::new(make_error_reply("InvalidRequest", "Missing arguments")),
|
None => {
|
||||||
|
return DispatchResult::new(make_error_reply(
|
||||||
|
"InvalidRequest",
|
||||||
|
"Missing arguments",
|
||||||
|
))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let attachment_id = match dict_get_str(args, "attachment_id") {
|
let attachment_id = match dict_get_str(args, "attachment_id") {
|
||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
None => return DispatchResult::new(make_error_reply("InvalidRequest", "Missing attachment_id")),
|
None => {
|
||||||
|
return DispatchResult::new(make_error_reply(
|
||||||
|
"InvalidRequest",
|
||||||
|
"Missing attachment_id",
|
||||||
|
))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let preview = dict_get_str(args, "preview")
|
let preview = dict_get_str(args, "preview")
|
||||||
.map(|s| s == "true")
|
.map(|s| s == "true")
|
||||||
@@ -371,11 +444,18 @@ pub async fn dispatch(
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
let args = match get_dictionary_field(root, "arguments") {
|
let args = match get_dictionary_field(root, "arguments") {
|
||||||
Some(a) => a,
|
Some(a) => a,
|
||||||
None => return DispatchResult::new(make_error_reply("InvalidRequest", "Missing arguments")),
|
None => {
|
||||||
|
return DispatchResult::new(make_error_reply(
|
||||||
|
"InvalidRequest",
|
||||||
|
"Missing arguments",
|
||||||
|
))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let path = match dict_get_str(args, "path") {
|
let path = match dict_get_str(args, "path") {
|
||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
None => return DispatchResult::new(make_error_reply("InvalidRequest", "Missing path")),
|
None => {
|
||||||
|
return DispatchResult::new(make_error_reply("InvalidRequest", "Missing path"))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
match agent
|
match agent
|
||||||
.send_event(|r| Event::UploadAttachment(PathBuf::from(path), r))
|
.send_event(|r| Event::UploadAttachment(PathBuf::from(path), r))
|
||||||
@@ -413,7 +493,12 @@ pub async fn dispatch(
|
|||||||
"UpdateSettings" => {
|
"UpdateSettings" => {
|
||||||
let args = match get_dictionary_field(root, "arguments") {
|
let args = match get_dictionary_field(root, "arguments") {
|
||||||
Some(a) => a,
|
Some(a) => a,
|
||||||
None => return DispatchResult::new(make_error_reply("InvalidRequest", "Missing arguments")),
|
None => {
|
||||||
|
return DispatchResult::new(make_error_reply(
|
||||||
|
"InvalidRequest",
|
||||||
|
"Missing arguments",
|
||||||
|
))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let server_url = dict_get_str(args, "server_url");
|
let server_url = dict_get_str(args, "server_url");
|
||||||
let username = dict_get_str(args, "username");
|
let username = dict_get_str(args, "username");
|
||||||
|
|||||||
@@ -146,8 +146,7 @@ impl ClientCli {
|
|||||||
|
|
||||||
loop {
|
loop {
|
||||||
match stream.next().await.unwrap() {
|
match stream.next().await.unwrap() {
|
||||||
Ok(update) => {
|
Ok(update) => match update {
|
||||||
match update {
|
|
||||||
SocketUpdate::Update(updates) => {
|
SocketUpdate::Update(updates) => {
|
||||||
for update in updates {
|
for update in updates {
|
||||||
println!("Got update: {:?}", update);
|
println!("Got update: {:?}", update);
|
||||||
@@ -156,7 +155,6 @@ impl ClientCli {
|
|||||||
SocketUpdate::Pong => {
|
SocketUpdate::Pong => {
|
||||||
println!("Pong");
|
println!("Pong");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
|||||||
@@ -209,4 +209,9 @@ impl DaemonInterface for DBusDaemonInterface {
|
|||||||
KordophoneRepository::mark_conversation_as_read(&self.proxy(), &conversation_id)
|
KordophoneRepository::mark_conversation_as_read(&self.proxy(), &conversation_id)
|
||||||
.map_err(|e| anyhow::anyhow!("Failed to mark conversation as read: {}", e))
|
.map_err(|e| anyhow::anyhow!("Failed to mark conversation as read: {}", e))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn test_notification(&mut self, summary: String, body: String) -> Result<()> {
|
||||||
|
KordophoneRepository::test_notification(&self.proxy(), &summary, &body)
|
||||||
|
.map_err(|e| anyhow::anyhow!("Failed to trigger test notification: {}", e))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ pub trait DaemonInterface {
|
|||||||
async fn download_attachment(&mut self, attachment_id: String) -> Result<()>;
|
async fn download_attachment(&mut self, attachment_id: String) -> Result<()>;
|
||||||
async fn upload_attachment(&mut self, path: String) -> Result<()>;
|
async fn upload_attachment(&mut self, path: String) -> Result<()>;
|
||||||
async fn mark_conversation_as_read(&mut self, conversation_id: String) -> Result<()>;
|
async fn mark_conversation_as_read(&mut self, conversation_id: String) -> Result<()>;
|
||||||
|
async fn test_notification(&mut self, summary: String, body: String) -> Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct StubDaemonInterface;
|
struct StubDaemonInterface;
|
||||||
@@ -112,6 +113,11 @@ impl DaemonInterface for StubDaemonInterface {
|
|||||||
"Daemon interface not implemented on this platform"
|
"Daemon interface not implemented on this platform"
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
async fn test_notification(&mut self, _summary: String, _body: String) -> Result<()> {
|
||||||
|
Err(anyhow::anyhow!(
|
||||||
|
"Daemon interface not implemented on this platform"
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_daemon_interface() -> Result<Box<dyn DaemonInterface>> {
|
pub fn new_daemon_interface() -> Result<Box<dyn DaemonInterface>> {
|
||||||
@@ -175,6 +181,9 @@ pub enum Commands {
|
|||||||
|
|
||||||
/// Marks a conversation as read.
|
/// Marks a conversation as read.
|
||||||
MarkConversationAsRead { conversation_id: String },
|
MarkConversationAsRead { conversation_id: String },
|
||||||
|
|
||||||
|
/// Displays a test notification using the daemon.
|
||||||
|
TestNotification { summary: String, body: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
#[derive(Subcommand)]
|
||||||
@@ -219,6 +228,9 @@ impl Commands {
|
|||||||
Commands::MarkConversationAsRead { conversation_id } => {
|
Commands::MarkConversationAsRead { conversation_id } => {
|
||||||
client.mark_conversation_as_read(conversation_id).await
|
client.mark_conversation_as_read(conversation_id).await
|
||||||
}
|
}
|
||||||
|
Commands::TestNotification { summary, body } => {
|
||||||
|
client.test_notification(summary, body).await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
use std::env;
|
use std::env;
|
||||||
use std::process;
|
use std::process;
|
||||||
|
|
||||||
use kordophone::{
|
|
||||||
api::{HTTPAPIClient, InMemoryAuthenticationStore, EventSocket},
|
|
||||||
model::{ConversationID, event::EventData},
|
|
||||||
APIInterface,
|
|
||||||
};
|
|
||||||
use kordophone::api::http_client::Credentials;
|
|
||||||
use kordophone::api::AuthenticationStore;
|
use kordophone::api::AuthenticationStore;
|
||||||
|
use kordophone::api::http_client::Credentials;
|
||||||
|
use kordophone::{
|
||||||
|
APIInterface,
|
||||||
|
api::{EventSocket, HTTPAPIClient, InMemoryAuthenticationStore},
|
||||||
|
model::{ConversationID, event::EventData},
|
||||||
|
};
|
||||||
|
|
||||||
use futures_util::StreamExt;
|
use futures_util::StreamExt;
|
||||||
use hyper::Uri;
|
use hyper::Uri;
|
||||||
@@ -18,7 +18,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
|
|
||||||
let args: Vec<String> = env::args().collect();
|
let args: Vec<String> = env::args().collect();
|
||||||
if args.len() < 2 {
|
if args.len() < 2 {
|
||||||
eprintln!("Usage: {} <conversation_id1> [conversation_id2] [conversation_id3] ...", args[0]);
|
eprintln!(
|
||||||
|
"Usage: {} <conversation_id1> [conversation_id2] [conversation_id3] ...",
|
||||||
|
args[0]
|
||||||
|
);
|
||||||
eprintln!("Environment variables required:");
|
eprintln!("Environment variables required:");
|
||||||
eprintln!(" KORDOPHONE_API_URL - Server URL");
|
eprintln!(" KORDOPHONE_API_URL - Server URL");
|
||||||
eprintln!(" KORDOPHONE_USERNAME - Username for authentication");
|
eprintln!(" KORDOPHONE_USERNAME - Username for authentication");
|
||||||
@@ -40,12 +43,14 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
let credentials = Credentials { username, password };
|
let credentials = Credentials { username, password };
|
||||||
|
|
||||||
// Collect all conversation IDs from command line arguments
|
// Collect all conversation IDs from command line arguments
|
||||||
let target_conversation_ids: Vec<ConversationID> = args[1..].iter()
|
let target_conversation_ids: Vec<ConversationID> =
|
||||||
.map(|id| id.clone())
|
args[1..].iter().map(|id| id.clone()).collect();
|
||||||
.collect();
|
|
||||||
|
|
||||||
println!("Monitoring {} conversation(s) for updates: {:?}",
|
println!(
|
||||||
target_conversation_ids.len(), target_conversation_ids);
|
"Monitoring {} conversation(s) for updates: {:?}",
|
||||||
|
target_conversation_ids.len(),
|
||||||
|
target_conversation_ids
|
||||||
|
);
|
||||||
|
|
||||||
let auth_store = InMemoryAuthenticationStore::new(Some(credentials.clone()));
|
let auth_store = InMemoryAuthenticationStore::new(Some(credentials.clone()));
|
||||||
let mut client = HTTPAPIClient::new(server_url, auth_store);
|
let mut client = HTTPAPIClient::new(server_url, auth_store);
|
||||||
@@ -62,26 +67,33 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
match event_result {
|
match event_result {
|
||||||
Ok(socket_event) => {
|
Ok(socket_event) => {
|
||||||
match socket_event {
|
match socket_event {
|
||||||
kordophone::api::event_socket::SocketEvent::Update(event) => {
|
kordophone::api::event_socket::SocketEvent::Update(event) => match event.data {
|
||||||
match event.data {
|
|
||||||
EventData::MessageReceived(conversation, _message) => {
|
EventData::MessageReceived(conversation, _message) => {
|
||||||
if target_conversation_ids.contains(&conversation.guid) {
|
if target_conversation_ids.contains(&conversation.guid) {
|
||||||
println!("Message update detected for conversation {}, marking as read...", conversation.guid);
|
println!(
|
||||||
|
"Message update detected for conversation {}, marking as read...",
|
||||||
|
conversation.guid
|
||||||
|
);
|
||||||
match client.mark_conversation_as_read(&conversation.guid).await {
|
match client.mark_conversation_as_read(&conversation.guid).await {
|
||||||
Ok(_) => println!("Successfully marked conversation {} as read", conversation.guid),
|
Ok(_) => println!(
|
||||||
Err(e) => eprintln!("Failed to mark conversation {} as read: {:?}", conversation.guid, e),
|
"Successfully marked conversation {} as read",
|
||||||
|
conversation.guid
|
||||||
|
),
|
||||||
|
Err(e) => eprintln!(
|
||||||
|
"Failed to mark conversation {} as read: {:?}",
|
||||||
|
conversation.guid, e
|
||||||
|
),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
kordophone::api::event_socket::SocketEvent::Pong => {
|
kordophone::api::event_socket::SocketEvent::Pong => {
|
||||||
// Ignore pong messages
|
// Ignore pong messages
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("Error receiving event: {:?}", e);
|
eprintln!("Error receiving event: {:?}", e);
|
||||||
break;
|
break;
|
||||||
|
|||||||
Reference in New Issue
Block a user