From 032573d23bdbddd4f52e920c744ca4599fcec255 Mon Sep 17 00:00:00 2001 From: James Magahern Date: Mon, 16 Jun 2025 19:26:13 -0700 Subject: [PATCH] cargo fmt --- kordophone-db/src/repository.rs | 23 +++--- kordophone/src/api/event_socket.rs | 7 +- kordophone/src/api/http_client.rs | 84 ++++++++++++--------- kordophone/src/model/update.rs | 4 +- kordophone/src/tests/test_client.rs | 7 +- kordophoned/src/daemon/attachment_store.rs | 21 +++--- kordophoned/src/daemon/mod.rs | 26 ++++--- kordophoned/src/daemon/models/attachment.rs | 3 +- kordophoned/src/daemon/update_monitor.rs | 7 +- kordophoned/src/dbus/mod.rs | 4 +- kordophoned/src/dbus/server_impl.rs | 13 ++-- kordophoned/src/main.rs | 29 +++++-- kpcli/src/client/mod.rs | 22 +++--- kpcli/src/daemon/mod.rs | 31 +++++--- 14 files changed, 168 insertions(+), 113 deletions(-) diff --git a/kordophone-db/src/repository.rs b/kordophone-db/src/repository.rs index 8c4102f..c37e956 100644 --- a/kordophone-db/src/repository.rs +++ b/kordophone-db/src/repository.rs @@ -168,8 +168,7 @@ impl<'a> Repository<'a> { let mut participant_cache: HashMap = HashMap::new(); // Prepare collections for the batch inserts. - let mut db_messages: Vec = - Vec::with_capacity(in_messages.len()); + let mut db_messages: Vec = Vec::with_capacity(in_messages.len()); let mut conv_msg_records: Vec = Vec::with_capacity(in_messages.len()); @@ -178,7 +177,8 @@ impl<'a> Repository<'a> { let sender_id = match &message.sender { Participant::Me => None, Participant::Remote { display_name, .. } => { - if let Some(cached_participant_id) = participant_cache.get(display_name) { + if let Some(cached_participant_id) = participant_cache.get(display_name) + { Some(*cached_participant_id) } else { // Try to load from DB first @@ -239,19 +239,22 @@ impl<'a> Repository<'a> { // processed instead of re-querying the DB. if let Some(last_msg) = db_messages.last() { use crate::schema::conversations::dsl as conv_dsl; - diesel::update(conv_dsl::conversations.filter(conv_dsl::id.eq(conversation_guid))) - .set(( - conv_dsl::date.eq(last_msg.date), - conv_dsl::last_message_preview.eq::>(Some(last_msg.text.clone())), - )) - .execute(conn)?; + diesel::update( + conv_dsl::conversations.filter(conv_dsl::id.eq(conversation_guid)), + ) + .set(( + conv_dsl::date.eq(last_msg.date), + conv_dsl::last_message_preview + .eq::>(Some(last_msg.text.clone())), + )) + .execute(conn)?; } Ok(()) })?; // TODO: May need to update conversation metadata here, but this has a perf impact. - // Ideally we would consolidate this in the code above, assuming we're only inserting *new* messages, but + // Ideally we would consolidate this in the code above, assuming we're only inserting *new* messages, but // this may not necessarily be the case. Ok(()) diff --git a/kordophone/src/api/event_socket.rs b/kordophone/src/api/event_socket.rs index 31ae740..de900fe 100644 --- a/kordophone/src/api/event_socket.rs +++ b/kordophone/src/api/event_socket.rs @@ -26,7 +26,12 @@ pub trait EventSocket { type UpdateStream: Stream>; /// Modern event pipeline - async fn events(self) -> (Self::EventStream, impl Sink); + async fn events( + self, + ) -> ( + Self::EventStream, + impl Sink, + ); /// Raw update items from the v1 API. async fn raw_updates(self) -> Self::UpdateStream; diff --git a/kordophone/src/api/http_client.rs b/kordophone/src/api/http_client.rs index 6bd29f8..265d72e 100644 --- a/kordophone/src/api/http_client.rs +++ b/kordophone/src/api/http_client.rs @@ -115,19 +115,26 @@ impl AuthSetting for hyper::http::Request { } } -type WebsocketSink = futures_util::stream::SplitSink>, tungstenite::Message>; -type WebsocketStream = futures_util::stream::SplitStream>>; +type WebsocketSink = futures_util::stream::SplitSink< + WebSocketStream>, + tungstenite::Message, +>; +type WebsocketStream = + futures_util::stream::SplitStream>>; pub struct WebsocketEventSocket { sink: Option, - stream: WebsocketStream + stream: WebsocketStream, } impl WebsocketEventSocket { pub fn new(socket: WebSocketStream>) -> Self { let (sink, stream) = socket.split(); - Self { sink: Some(sink), stream } + Self { + sink: Some(sink), + stream, + } } } @@ -147,12 +154,10 @@ impl WebsocketEventSocket { } } tungstenite::Message::Ping(_) => { - // We don't expect the server to send us pings. + // We don't expect the server to send us pings. Ok(None) } - tungstenite::Message::Pong(_) => { - Ok(Some(SocketUpdate::Pong)) - } + tungstenite::Message::Pong(_) => Ok(Some(SocketUpdate::Pong)), tungstenite::Message::Close(_) => { // Connection was closed cleanly Err(Error::ClientError("WebSocket connection closed".into())) @@ -169,33 +174,40 @@ impl EventSocket for WebsocketEventSocket { type EventStream = BoxStream<'static, Result>; type UpdateStream = BoxStream<'static, Result>; - async fn events(mut self) -> (Self::EventStream, impl Sink) { + async fn events( + mut self, + ) -> ( + Self::EventStream, + impl Sink, + ) { use futures_util::stream::iter; - let sink = self.sink.take().unwrap().with(|f| { - match f { - SinkMessage::Ping => futures_util::future::ready(Ok::(tungstenite::Message::Ping(Bytes::new()))) - } + let sink = self.sink.take().unwrap().with(|f| match f { + SinkMessage::Ping => futures_util::future::ready(Ok::( + tungstenite::Message::Ping(Bytes::new()), + )), }); - let stream = self.raw_update_stream() - .map_ok(|updates| -> BoxStream<'static, Result> { - match updates { - SocketUpdate::Update(updates) => { - let iter_stream = iter( - updates.into_iter().map(|u| Ok(SocketEvent::Update(Event::from(u)))) - ); - iter_stream.boxed() + let stream = self + .raw_update_stream() + .map_ok( + |updates| -> BoxStream<'static, Result> { + match updates { + SocketUpdate::Update(updates) => { + let iter_stream = iter( + updates + .into_iter() + .map(|u| Ok(SocketEvent::Update(Event::from(u)))), + ); + iter_stream.boxed() + } + SocketUpdate::Pong => iter(std::iter::once(Ok(SocketEvent::Pong))).boxed(), } - SocketUpdate::Pong => { - iter(std::iter::once(Ok(SocketEvent::Pong))).boxed() - } - } - }) + }, + ) .try_flatten() .boxed(); - (stream, sink) } @@ -212,9 +224,7 @@ impl Stream for ResponseStream { type Item = Result; fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - self.body - .poll_next_unpin(cx) - .map_err(Error::HTTPError) + self.body.poll_next_unpin(cx).map_err(Error::HTTPError) } } @@ -328,10 +338,10 @@ impl APIInterface for HTTPAPIClient { guid: String, } - // TODO: We can still use Body::wrap_stream here, but we need to make sure to plumb the CONTENT_LENGTH header, - // otherwise CocoaHTTPServer will crash because of a bug. - // - // See ff03e73758f30c081a9319a8c04025cba69b8393 for what this was like before. + // TODO: We can still use Body::wrap_stream here, but we need to make sure to plumb the CONTENT_LENGTH header, + // otherwise CocoaHTTPServer will crash because of a bug. + // + // See ff03e73758f30c081a9319a8c04025cba69b8393 for what this was like before. let mut bytes = Vec::new(); data.read_to_end(&mut bytes) .await @@ -578,7 +588,11 @@ impl HTTPAPIClient { _ => { let status = response.status(); let body_str = hyper::body::to_bytes(response.into_body()).await?; - let message = format!("Request failed ({:}). Response body: {:?}", status, String::from_utf8_lossy(&body_str)); + let message = format!( + "Request failed ({:}). Response body: {:?}", + status, + String::from_utf8_lossy(&body_str) + ); return Err(Error::ClientError(message)); } } diff --git a/kordophone/src/model/update.rs b/kordophone/src/model/update.rs index b61ef5d..fc2061b 100644 --- a/kordophone/src/model/update.rs +++ b/kordophone/src/model/update.rs @@ -2,8 +2,7 @@ use super::conversation::Conversation; use super::message::Message; use serde::Deserialize; -#[derive(Debug, Clone, Deserialize)] -#[derive(Default)] +#[derive(Debug, Clone, Deserialize, Default)] pub struct UpdateItem { #[serde(rename = "messageSequenceNumber")] pub seq: u64, @@ -17,4 +16,3 @@ pub struct UpdateItem { #[serde(default)] pub pong: bool, } - diff --git a/kordophone/src/tests/test_client.rs b/kordophone/src/tests/test_client.rs index 0ab53d3..3709855 100644 --- a/kordophone/src/tests/test_client.rs +++ b/kordophone/src/tests/test_client.rs @@ -56,7 +56,12 @@ impl EventSocket for TestEventSocket { type EventStream = BoxStream<'static, Result>; type UpdateStream = BoxStream<'static, Result>; - async fn events(self) -> (Self::EventStream, impl Sink) { + async fn events( + self, + ) -> ( + Self::EventStream, + impl Sink, + ) { ( futures_util::stream::iter(self.events.into_iter().map(Ok)).boxed(), futures_util::sink::sink(), diff --git a/kordophoned/src/daemon/attachment_store.rs b/kordophoned/src/daemon/attachment_store.rs index 0ba3df3..8158c6a 100644 --- a/kordophoned/src/daemon/attachment_store.rs +++ b/kordophoned/src/daemon/attachment_store.rs @@ -114,11 +114,11 @@ impl AttachmentStore { store_path: &PathBuf, database: &mut Arc>, daemon_event_sink: &Sender, - guid: &String, - preview: bool + guid: &String, + preview: bool, ) -> Result<()> { let attachment = Self::get_attachment_impl(store_path, guid); - + if attachment.is_downloaded(preview) { log::info!(target: target::ATTACHMENTS, "Attachment already downloaded: {}", attachment.guid); return Err(AttachmentStoreError::AttachmentAlreadyDownloaded.into()); @@ -144,13 +144,16 @@ impl AttachmentStore { while let Some(Ok(data)) = stream.next().await { writer.write(data.as_ref())?; } - + // Flush and sync the temporary file before moving writer.flush()?; file.sync_all()?; // Atomically move the temporary file to the final location - std::fs::rename(&temporary_path, &attachment.get_path_for_preview_scratch(preview, false))?; + std::fs::rename( + &temporary_path, + &attachment.get_path_for_preview_scratch(preview, false), + )?; log::info!(target: target::ATTACHMENTS, "Completed download for attachment: {}", attachment.guid); @@ -175,12 +178,12 @@ impl AttachmentStore { let uploads_path = store_path.join("uploads"); std::fs::create_dir_all(&uploads_path).unwrap(); - // First, copy the file to the store path, under /uploads/. + // First, copy the file to the store path, under /uploads/. log::trace!(target: target::ATTACHMENTS, "Copying attachment to uploads directory: {}", uploads_path.display()); let temporary_path = uploads_path.join(incoming_path.file_name().unwrap()); std::fs::copy(incoming_path, &temporary_path).unwrap(); - // Open file handle to the temporary file, + // Open file handle to the temporary file, log::trace!(target: target::ATTACHMENTS, "Opening stream to temporary file: {}", temporary_path.display()); let file = File::open(&temporary_path).await?; let reader: BufReader = BufReader::new(file); @@ -189,7 +192,7 @@ impl AttachmentStore { let filename = incoming_path.file_name().unwrap().to_str().unwrap(); log::trace!(target: target::ATTACHMENTS, "Uploading attachment to server: {}", &filename); let mut client = Daemon::get_client_impl(database).await?; - + let metadata = std::fs::metadata(&temporary_path)?; let size = metadata.len(); let guid = client.upload_attachment(reader, filename, size).await?; @@ -220,7 +223,7 @@ impl AttachmentStore { let daemon_event_sink = self.daemon_event_sink.clone(); let _guid = guid.clone(); - // Spawn a new task here so we don't block incoming queue events. + // Spawn a new task here so we don't block incoming queue events. tokio::spawn(async move { let result = Self::download_attachment_impl( &store_path, diff --git a/kordophoned/src/daemon/mod.rs b/kordophoned/src/daemon/mod.rs index 4428f4a..ed2edb9 100644 --- a/kordophoned/src/daemon/mod.rs +++ b/kordophoned/src/daemon/mod.rs @@ -224,7 +224,7 @@ impl Daemon { Event::UpdateStreamReconnected => { log::info!(target: target::UPDATES, "Update stream reconnected"); - // The ui client will respond differently, but we'll almost certainly want to do a sync-list in response to this. + // The ui client will respond differently, but we'll almost certainly want to do a sync-list in response to this. self.spawn_conversation_list_sync(); // Send signal to the client that the update stream has been reconnected. @@ -267,12 +267,14 @@ impl Daemon { self.spawn_conversation_list_sync(); // Also restart the update monitor. - if let Err(e) = self.update_monitor_command_tx + if let Err(e) = self + .update_monitor_command_tx .as_ref() .unwrap() - .try_send(UpdateMonitorCommand::Restart) { - log::warn!(target: target::UPDATES, "Failed to send restart command to update monitor: {}", e); - } + .try_send(UpdateMonitorCommand::Restart) + { + log::warn!(target: target::UPDATES, "Failed to send restart command to update monitor: {}", e); + } } reply.send(()).unwrap(); @@ -428,7 +430,12 @@ impl Daemon { .await } - async fn enqueue_outgoing_message(&mut self, text: String, conversation_id: String, attachment_guids: Vec) -> Uuid { + async fn enqueue_outgoing_message( + &mut self, + text: String, + conversation_id: String, + attachment_guids: Vec, + ) -> Uuid { let conversation_id = conversation_id.clone(); let outgoing_message = OutgoingMessage::builder() .text(text) @@ -553,11 +560,12 @@ impl Daemon { .await?; // Filter messages that have an empty body, or a body that is just whitespace. - // This is a workaround for a bug in the server where it returns messages with an empty body, which is usually - // the typing indicator or stuff like that. In the future, we need to move to ChatItems instead of Messages. + // This is a workaround for a bug in the server where it returns messages with an empty body, which is usually + // the typing indicator or stuff like that. In the future, we need to move to ChatItems instead of Messages. let insertable_messages: Vec = messages .into_iter() - .filter(|m| !m.text.is_empty() && !m.text.trim().is_empty()).collect(); + .filter(|m| !m.text.is_empty() && !m.text.trim().is_empty()) + .collect(); let db_messages: Vec = insertable_messages .into_iter() diff --git a/kordophoned/src/daemon/models/attachment.rs b/kordophoned/src/daemon/models/attachment.rs index 6e0b95a..777fff7 100644 --- a/kordophoned/src/daemon/models/attachment.rs +++ b/kordophoned/src/daemon/models/attachment.rs @@ -30,7 +30,8 @@ impl Attachment { pub fn get_path_for_preview_scratch(&self, preview: bool, scratch: bool) -> PathBuf { let extension = if preview { "preview" } else { "full" }; if scratch { - self.base_path.with_extension(format!("{}.download", extension)) + self.base_path + .with_extension(format!("{}.download", extension)) } else { self.base_path.with_extension(extension) } diff --git a/kordophoned/src/daemon/update_monitor.rs b/kordophoned/src/daemon/update_monitor.rs index 12ae643..3b0da7c 100644 --- a/kordophoned/src/daemon/update_monitor.rs +++ b/kordophoned/src/daemon/update_monitor.rs @@ -40,7 +40,7 @@ impl UpdateMonitor { event_sender, last_sync_times: HashMap::new(), update_seq: None, - first_connection: false, // optimistic assumption that we're not reconnecting the first time. + first_connection: false, // optimistic assumption that we're not reconnecting the first time. command_tx: Some(command_tx), command_rx, } @@ -50,10 +50,7 @@ impl UpdateMonitor { self.command_tx.take().unwrap() } - async fn send_event( - &self, - make_event: impl FnOnce(Reply) -> Event, - ) -> DaemonResult { + async fn send_event(&self, make_event: impl FnOnce(Reply) -> Event) -> DaemonResult { let (reply_tx, reply_rx) = tokio::sync::oneshot::channel(); self.event_sender .send(make_event(reply_tx)) diff --git a/kordophoned/src/dbus/mod.rs b/kordophoned/src/dbus/mod.rs index 4edc275..5efd0d2 100644 --- a/kordophoned/src/dbus/mod.rs +++ b/kordophoned/src/dbus/mod.rs @@ -10,10 +10,10 @@ pub mod interface { include!(concat!(env!("OUT_DIR"), "/kordophone-server.rs")); pub mod signals { - pub use crate::interface::NetBuzzertKordophoneRepositoryConversationsUpdated as ConversationsUpdated; - pub use crate::interface::NetBuzzertKordophoneRepositoryMessagesUpdated as MessagesUpdated; pub use crate::interface::NetBuzzertKordophoneRepositoryAttachmentDownloadCompleted as AttachmentDownloadCompleted; pub use crate::interface::NetBuzzertKordophoneRepositoryAttachmentUploadCompleted as AttachmentUploadCompleted; + pub use crate::interface::NetBuzzertKordophoneRepositoryConversationsUpdated as ConversationsUpdated; + pub use crate::interface::NetBuzzertKordophoneRepositoryMessagesUpdated as MessagesUpdated; pub use crate::interface::NetBuzzertKordophoneRepositoryUpdateStreamReconnected as UpdateStreamReconnected; } } diff --git a/kordophoned/src/dbus/server_impl.rs b/kordophoned/src/dbus/server_impl.rs index 98ff227..6bcc241 100644 --- a/kordophoned/src/dbus/server_impl.rs +++ b/kordophoned/src/dbus/server_impl.rs @@ -128,11 +128,11 @@ impl DbusRepository for ServerImpl { let mut map = arg::PropMap::new(); map.insert("id".into(), arg::Variant(Box::new(msg.id))); - // xxx: Remove the attachment placeholder here. + // xxx: Remove the attachment placeholder here. // This is not the ideal place to do this, but once we start using ChatItems instead of IMMessages - // from the server, we shouldn't be seeing these placeholders. + // from the server, we shouldn't be seeing these placeholders. let text = msg.text.replace("\u{FFFC}", ""); - + map.insert("text".into(), arg::Variant(Box::new(text))); map.insert( "date".into(), @@ -272,12 +272,9 @@ impl DbusRepository for ServerImpl { self.send_event_sync(|r| Event::DownloadAttachment(attachment_id, preview, r)) } - fn upload_attachment( - &mut self, - path: String, - ) -> Result { + fn upload_attachment(&mut self, path: String) -> Result { use std::path::PathBuf; - + let path = PathBuf::from(path); self.send_event_sync(|r| Event::UploadAttachment(path, r)) } diff --git a/kordophoned/src/main.rs b/kordophoned/src/main.rs index 59db0fb..3ee1608 100644 --- a/kordophoned/src/main.rs +++ b/kordophoned/src/main.rs @@ -100,9 +100,15 @@ async fn main() { } Signal::AttachmentDownloaded(attachment_id) => { - log::debug!("Sending signal: AttachmentDownloaded for attachment {}", attachment_id); + log::debug!( + "Sending signal: AttachmentDownloaded for attachment {}", + attachment_id + ); dbus_registry - .send_signal(interface::OBJECT_PATH, DbusSignals::AttachmentDownloadCompleted { attachment_id }) + .send_signal( + interface::OBJECT_PATH, + DbusSignals::AttachmentDownloadCompleted { attachment_id }, + ) .unwrap_or_else(|_| { log::error!("Failed to send signal"); 0 @@ -110,9 +116,19 @@ async fn main() { } Signal::AttachmentUploaded(upload_guid, attachment_guid) => { - log::debug!("Sending signal: AttachmentUploaded for upload {}, attachment {}", upload_guid, attachment_guid); + log::debug!( + "Sending signal: AttachmentUploaded for upload {}, attachment {}", + upload_guid, + attachment_guid + ); dbus_registry - .send_signal(interface::OBJECT_PATH, DbusSignals::AttachmentUploadCompleted { upload_guid, attachment_guid }) + .send_signal( + interface::OBJECT_PATH, + DbusSignals::AttachmentUploadCompleted { + upload_guid, + attachment_guid, + }, + ) .unwrap_or_else(|_| { log::error!("Failed to send signal"); 0 @@ -122,7 +138,10 @@ async fn main() { Signal::UpdateStreamReconnected => { log::debug!("Sending signal: UpdateStreamReconnected"); dbus_registry - .send_signal(interface::OBJECT_PATH, DbusSignals::UpdateStreamReconnected {}) + .send_signal( + interface::OBJECT_PATH, + DbusSignals::UpdateStreamReconnected {}, + ) .unwrap_or_else(|_| { log::error!("Failed to send signal"); 0 diff --git a/kpcli/src/client/mod.rs b/kpcli/src/client/mod.rs index 8709d89..4ee5eaa 100644 --- a/kpcli/src/client/mod.rs +++ b/kpcli/src/client/mod.rs @@ -113,19 +113,17 @@ impl ClientCli { let (mut stream, _) = socket.events().await; while let Some(Ok(socket_event)) = stream.next().await { match socket_event { - SocketEvent::Update(event) => { - match event.data { - EventData::ConversationChanged(conversation) => { - println!("Conversation changed: {}", conversation.guid); - } - EventData::MessageReceived(conversation, message) => { - println!( - "Message received: msg: {} conversation: {}", - message.guid, conversation.guid - ); - } + SocketEvent::Update(event) => match event.data { + EventData::ConversationChanged(conversation) => { + println!("Conversation changed: {}", conversation.guid); } - } + EventData::MessageReceived(conversation, message) => { + println!( + "Message received: msg: {} conversation: {}", + message.guid, conversation.guid + ); + } + }, SocketEvent::Pong => { println!("Pong"); } diff --git a/kpcli/src/daemon/mod.rs b/kpcli/src/daemon/mod.rs index 974a7df..b67fb3d 100644 --- a/kpcli/src/daemon/mod.rs +++ b/kpcli/src/daemon/mod.rs @@ -54,14 +54,10 @@ pub enum Commands { }, /// Downloads an attachment from the server to the attachment store. Returns the path to the attachment. - DownloadAttachment { - attachment_id: String, - }, + DownloadAttachment { attachment_id: String }, /// Uploads an attachment to the server, returns upload guid. - UploadAttachment { - path: String, - }, + UploadAttachment { path: String }, } #[derive(Subcommand)] @@ -100,7 +96,9 @@ impl Commands { text, } => client.enqueue_outgoing_message(conversation_id, text).await, Commands::UploadAttachment { path } => client.upload_attachment(path).await, - Commands::DownloadAttachment { attachment_id } => client.download_attachment(attachment_id).await, + Commands::DownloadAttachment { attachment_id } => { + client.download_attachment(attachment_id).await + } } } } @@ -178,8 +176,12 @@ impl DaemonCli { text: String, ) -> Result<()> { let attachment_guids: Vec<&str> = vec![]; - let outgoing_message_id = - KordophoneRepository::send_message(&self.proxy(), &conversation_id, &text, attachment_guids)?; + let outgoing_message_id = KordophoneRepository::send_message( + &self.proxy(), + &conversation_id, + &text, + attachment_guids, + )?; println!("Outgoing message ID: {}", outgoing_message_id); Ok(()) } @@ -244,7 +246,8 @@ impl DaemonCli { KordophoneRepository::download_attachment(&self.proxy(), &attachment_id, false)?; // Get attachment info. - let attachment_info = KordophoneRepository::get_attachment_info(&self.proxy(), &attachment_id)?; + let attachment_info = + KordophoneRepository::get_attachment_info(&self.proxy(), &attachment_id)?; let (path, preview_path, downloaded, preview_downloaded) = attachment_info; if downloaded { @@ -256,14 +259,18 @@ impl DaemonCli { // Attach to the signal that the attachment has been downloaded. let _id = self.proxy().match_signal( - move |h: dbus_interface::NetBuzzertKordophoneRepositoryAttachmentDownloadCompleted, _: &Connection, _: &dbus::message::Message| { + move |h: dbus_interface::NetBuzzertKordophoneRepositoryAttachmentDownloadCompleted, + _: &Connection, + _: &dbus::message::Message| { println!("Signal: Attachment downloaded: {}", path); std::process::exit(0); }, ); let _id = self.proxy().match_signal( - |h: dbus_interface::NetBuzzertKordophoneRepositoryAttachmentDownloadFailed, _: &Connection, _: &dbus::message::Message| { + |h: dbus_interface::NetBuzzertKordophoneRepositoryAttachmentDownloadFailed, + _: &Connection, + _: &dbus::message::Message| { println!("Signal: Attachment download failed: {}", h.attachment_id); std::process::exit(1); },