diff --git a/kordophone-db/src/database.rs b/kordophone-db/src/database.rs index d7eb7d3..b0fe629 100644 --- a/kordophone-db/src/database.rs +++ b/kordophone-db/src/database.rs @@ -1,6 +1,6 @@ use anyhow::Result; -use diesel::prelude::*; use async_trait::async_trait; +use diesel::prelude::*; pub use std::sync::Arc; pub use tokio::sync::Mutex; @@ -31,7 +31,8 @@ pub struct Database { impl Database { pub fn new(path: &str) -> Result { let mut connection = SqliteConnection::establish(path)?; - connection.run_pending_migrations(MIGRATIONS) + connection + .run_pending_migrations(MIGRATIONS) .map_err(|e| anyhow::anyhow!("Error running migrations: {}", e))?; Ok(Self { connection }) diff --git a/kordophone-db/src/lib.rs b/kordophone-db/src/lib.rs index abdc8f7..e026a7d 100644 --- a/kordophone-db/src/lib.rs +++ b/kordophone-db/src/lib.rs @@ -7,4 +7,4 @@ pub mod settings; #[cfg(test)] mod tests; -pub use repository::Repository; \ No newline at end of file +pub use repository::Repository; diff --git a/kordophone-db/src/models/conversation.rs b/kordophone-db/src/models/conversation.rs index 85a9a96..c7de2de 100644 --- a/kordophone-db/src/models/conversation.rs +++ b/kordophone-db/src/models/conversation.rs @@ -1,6 +1,6 @@ +use crate::models::participant::Participant; use chrono::{DateTime, NaiveDateTime}; use uuid::Uuid; -use crate::models::participant::Participant; #[derive(Clone, Debug)] pub struct Conversation { @@ -33,18 +33,17 @@ impl From for Conversation { fn from(value: kordophone::model::Conversation) -> Self { Self { guid: value.guid, - unread_count: u16::try_from(value.unread_count).unwrap(), + unread_count: u16::try_from(value.unread_count).unwrap(), display_name: value.display_name, last_message_preview: value.last_message_preview, date: DateTime::from_timestamp( value.date.unix_timestamp(), - value.date.unix_timestamp_nanos() - .try_into() - .unwrap_or(0), - ) - .unwrap() - .naive_local(), - participants: value.participant_display_names + value.date.unix_timestamp_nanos().try_into().unwrap_or(0), + ) + .unwrap() + .naive_local(), + participants: value + .participant_display_names .into_iter() .map(|p| p.into()) .collect(), diff --git a/kordophone-db/src/models/db/conversation.rs b/kordophone-db/src/models/db/conversation.rs index f1b37c5..f249265 100644 --- a/kordophone-db/src/models/db/conversation.rs +++ b/kordophone-db/src/models/db/conversation.rs @@ -1,9 +1,6 @@ -use diesel::prelude::*; +use crate::models::{db::participant::InsertableRecord as InsertableParticipant, Conversation}; use chrono::NaiveDateTime; -use crate::models::{ - Conversation, - db::participant::InsertableRecord as InsertableParticipant, -}; +use diesel::prelude::*; #[derive(Queryable, Selectable, Insertable, AsChangeset, Clone, Identifiable)] #[diesel(table_name = crate::schema::conversations)] @@ -33,11 +30,11 @@ impl From for (Record, Vec) { fn from(conversation: Conversation) -> Self { ( Record::from(conversation.clone()), - - conversation.participants + conversation + .participants .into_iter() .map(InsertableParticipant::from) - .collect() + .collect(), ) } } @@ -53,4 +50,4 @@ impl From for Conversation { participants: vec![], } } -} \ No newline at end of file +} diff --git a/kordophone-db/src/models/db/message.rs b/kordophone-db/src/models/db/message.rs index 736c28d..9ca6e72 100644 --- a/kordophone-db/src/models/db/message.rs +++ b/kordophone-db/src/models/db/message.rs @@ -1,6 +1,6 @@ -use diesel::prelude::*; -use chrono::NaiveDateTime; use crate::models::{Message, Participant}; +use chrono::NaiveDateTime; +use diesel::prelude::*; #[derive(Queryable, Selectable, Insertable, AsChangeset, Clone, Identifiable, Debug)] #[diesel(table_name = crate::schema::messages)] @@ -21,10 +21,11 @@ impl From for Record { } else { Some(serde_json::to_string(&message.file_transfer_guids).unwrap_or_default()) }; - - let attachment_metadata = message.attachment_metadata + + let attachment_metadata = message + .attachment_metadata .map(|metadata| serde_json::to_string(&metadata).unwrap_or_default()); - + Self { id: message.id, sender_participant_id: match message.sender { @@ -41,13 +42,15 @@ impl From for Record { impl From for Message { fn from(record: Record) -> Self { - let file_transfer_guids = record.file_transfer_guids + let file_transfer_guids = record + .file_transfer_guids .and_then(|json| serde_json::from_str(&json).ok()) .unwrap_or_default(); - - let attachment_metadata = record.attachment_metadata + + let attachment_metadata = record + .attachment_metadata .and_then(|json| serde_json::from_str(&json).ok()); - + Self { id: record.id, // We'll set the proper sender later when loading participant info @@ -59,4 +62,3 @@ impl From for Message { } } } - diff --git a/kordophone-db/src/models/db/mod.rs b/kordophone-db/src/models/db/mod.rs index eeedf6c..7cbefc7 100644 --- a/kordophone-db/src/models/db/mod.rs +++ b/kordophone-db/src/models/db/mod.rs @@ -1,3 +1,3 @@ pub mod conversation; +pub mod message; pub mod participant; -pub mod message; \ No newline at end of file diff --git a/kordophone-db/src/models/db/participant.rs b/kordophone-db/src/models/db/participant.rs index e40dae9..0ce7150 100644 --- a/kordophone-db/src/models/db/participant.rs +++ b/kordophone-db/src/models/db/participant.rs @@ -1,6 +1,6 @@ -use diesel::prelude::*; use crate::models::Participant; use crate::schema::conversation_participants; +use diesel::prelude::*; #[derive(Queryable, Selectable, AsChangeset, Identifiable)] #[diesel(table_name = crate::schema::participants)] @@ -27,7 +27,7 @@ impl From for InsertableRecord { Participant::Remote { display_name, .. } => InsertableRecord { display_name: Some(display_name), is_me: false, - } + }, } } } @@ -67,7 +67,7 @@ impl From for Record { id: 0, // This will be set by the database display_name: Some(display_name), is_me: false, - } + }, } } -} \ No newline at end of file +} diff --git a/kordophone-db/src/models/message.rs b/kordophone-db/src/models/message.rs index 100ffcc..d57e086 100644 --- a/kordophone-db/src/models/message.rs +++ b/kordophone-db/src/models/message.rs @@ -1,11 +1,11 @@ +use crate::models::participant::Participant; use chrono::{DateTime, NaiveDateTime}; +use kordophone::model::message::AttachmentMetadata; +use kordophone::model::outgoing_message::OutgoingMessage; use std::collections::HashMap; use uuid::Uuid; -use crate::models::participant::Participant; -use kordophone::model::outgoing_message::OutgoingMessage; -use kordophone::model::message::AttachmentMetadata; -#[derive(Clone, Debug)] +#[derive(Clone, Debug)] pub struct Message { pub id: String, pub sender: Participant, @@ -35,12 +35,10 @@ impl From for Message { text: value.text, date: DateTime::from_timestamp( value.date.unix_timestamp(), - value.date.unix_timestamp_nanos() - .try_into() - .unwrap_or(0), - ) - .unwrap() - .naive_local(), + value.date.unix_timestamp_nanos().try_into().unwrap_or(0), + ) + .unwrap() + .naive_local(), file_transfer_guids: value.file_transfer_guids, attachment_metadata: value.attachment_metadata, } @@ -107,7 +105,10 @@ impl MessageBuilder { self } - pub fn attachment_metadata(mut self, attachment_metadata: HashMap) -> Self { + pub fn attachment_metadata( + mut self, + attachment_metadata: HashMap, + ) -> Self { self.attachment_metadata = Some(attachment_metadata); self } @@ -123,4 +124,3 @@ impl MessageBuilder { } } } - diff --git a/kordophone-db/src/models/mod.rs b/kordophone-db/src/models/mod.rs index 206eb44..13571fc 100644 --- a/kordophone-db/src/models/mod.rs +++ b/kordophone-db/src/models/mod.rs @@ -1,8 +1,8 @@ pub mod conversation; -pub mod participant; -pub mod message; pub mod db; +pub mod message; +pub mod participant; pub use conversation::Conversation; +pub use message::Message; pub use participant::Participant; -pub use message::Message; \ No newline at end of file diff --git a/kordophone-db/src/repository.rs b/kordophone-db/src/repository.rs index eb18fad..c537e07 100644 --- a/kordophone-db/src/repository.rs +++ b/kordophone-db/src/repository.rs @@ -4,16 +4,13 @@ use diesel::query_dsl::BelongingToDsl; use crate::{ models::{ - Conversation, - Message, - Participant, db::conversation::Record as ConversationRecord, - db::participant::{ - ConversationParticipant, - Record as ParticipantRecord, - InsertableRecord as InsertableParticipantRecord - }, db::message::Record as MessageRecord, + db::participant::{ + ConversationParticipant, InsertableRecord as InsertableParticipantRecord, + Record as ParticipantRecord, + }, + Conversation, Message, Participant, }, schema, }; @@ -28,9 +25,9 @@ impl<'a> Repository<'a> { } pub fn insert_conversation(&mut self, conversation: Conversation) -> Result<()> { + use crate::schema::conversation_participants::dsl::*; use crate::schema::conversations::dsl::*; use crate::schema::participants::dsl::*; - use crate::schema::conversation_participants::dsl::*; let (db_conversation, db_participants) = conversation.into(); @@ -76,7 +73,8 @@ impl<'a> Repository<'a> { .load::(self.connection)?; let mut model_conversation: Conversation = conversation.into(); - model_conversation.participants = db_participants.into_iter().map(|p| p.into()).collect(); + model_conversation.participants = + db_participants.into_iter().map(|p| p.into()).collect(); return Ok(Some(model_conversation)); } @@ -102,7 +100,8 @@ impl<'a> Repository<'a> { .load::(self.connection)?; let mut model_conversation: Conversation = db_conversation.into(); - model_conversation.participants = db_participants.into_iter().map(|p| p.into()).collect(); + model_conversation.participants = + db_participants.into_iter().map(|p| p.into()).collect(); result.push(model_conversation); } @@ -111,8 +110,8 @@ impl<'a> Repository<'a> { } pub fn insert_message(&mut self, conversation_guid: &str, message: Message) -> Result<()> { - use crate::schema::messages::dsl::*; use crate::schema::conversation_messages::dsl::*; + use crate::schema::messages::dsl::*; // Handle participant if message has a remote sender let sender = message.sender.clone(); @@ -136,9 +135,13 @@ impl<'a> Repository<'a> { Ok(()) } - pub fn insert_messages(&mut self, conversation_guid: &str, in_messages: Vec) -> Result<()> { - use crate::schema::messages::dsl::*; + pub fn insert_messages( + &mut self, + conversation_guid: &str, + in_messages: Vec, + ) -> Result<()> { use crate::schema::conversation_messages::dsl::*; + use crate::schema::messages::dsl::*; // Local insertable struct for the join table #[derive(Insertable)] @@ -154,7 +157,8 @@ impl<'a> Repository<'a> { // Build the collections of insertable records let mut db_messages: Vec = Vec::with_capacity(in_messages.len()); - let mut conv_msg_records: Vec = Vec::with_capacity(in_messages.len()); + let mut conv_msg_records: Vec = + Vec::with_capacity(in_messages.len()); for message in in_messages { // Handle participant if message has a remote sender @@ -186,9 +190,12 @@ impl<'a> Repository<'a> { Ok(()) } - pub fn get_messages_for_conversation(&mut self, conversation_guid: &str) -> Result> { - use crate::schema::messages::dsl::*; + pub fn get_messages_for_conversation( + &mut self, + conversation_guid: &str, + ) -> Result> { use crate::schema::conversation_messages::dsl::*; + use crate::schema::messages::dsl::*; use crate::schema::participants::dsl::*; let message_records = conversation_messages @@ -201,7 +208,7 @@ impl<'a> Repository<'a> { let mut result = Vec::new(); for message_record in message_records { let mut message: Message = message_record.clone().into(); - + // If there's a sender_participant_id, load the participant info if let Some(pid) = message_record.sender_participant_id { let participant = participants @@ -216,9 +223,12 @@ impl<'a> Repository<'a> { Ok(result) } - pub fn get_last_message_for_conversation(&mut self, conversation_guid: &str) -> Result> { - use crate::schema::messages::dsl::*; + pub fn get_last_message_for_conversation( + &mut self, + conversation_guid: &str, + ) -> Result> { use crate::schema::conversation_messages::dsl::*; + use crate::schema::messages::dsl::*; let message_record = conversation_messages .filter(conversation_id.eq(conversation_guid)) @@ -247,7 +257,11 @@ impl<'a> Repository<'a> { let conversation = self.get_conversation_by_guid(conversation_guid)?; if let Some(mut conversation) = conversation { if let Some(last_message) = self.get_last_message_for_conversation(conversation_guid)? { - log::debug!("Updating conversation metadata: {} message: {:?}", conversation_guid, last_message); + log::debug!( + "Updating conversation metadata: {} message: {:?}", + conversation_guid, + last_message + ); conversation.date = last_message.date; conversation.last_message_preview = Some(last_message.text.clone()); self.insert_conversation(conversation)?; @@ -261,14 +275,21 @@ impl<'a> Repository<'a> { // This is a workaround since the Sqlite backend doesn't support `RETURNING` // Huge caveat with this is that it depends on whatever the last insert was, prevents concurrent inserts. fn last_insert_id(&mut self) -> Result { - Ok(diesel::select(diesel::dsl::sql::("last_insert_rowid()")) - .get_result(self.connection)?) + Ok( + diesel::select(diesel::dsl::sql::( + "last_insert_rowid()", + )) + .get_result(self.connection)?, + ) } fn get_or_create_participant(&mut self, participant: &Participant) -> Option { match participant { Participant::Me => None, - Participant::Remote { display_name: p_name, .. } => { + Participant::Remote { + display_name: p_name, + .. + } => { use crate::schema::participants::dsl::*; let existing_participant = participants diff --git a/kordophone-db/src/schema.rs b/kordophone-db/src/schema.rs index 1cf6f3a..d0c5355 100644 --- a/kordophone-db/src/schema.rs +++ b/kordophone-db/src/schema.rs @@ -28,9 +28,9 @@ diesel::table! { diesel::table! { messages (id) { - id -> Text, // guid - text -> Text, - sender_participant_id -> Nullable, + id -> Text, // guid + text -> Text, + sender_participant_id -> Nullable, date -> Timestamp, file_transfer_guids -> Nullable, // JSON array of file transfer GUIDs attachment_metadata -> Nullable, // JSON string of attachment metadata @@ -53,8 +53,12 @@ diesel::table! { diesel::joinable!(conversation_participants -> conversations (conversation_id)); diesel::joinable!(conversation_participants -> participants (participant_id)); -diesel::allow_tables_to_appear_in_same_query!(conversations, participants, conversation_participants); +diesel::allow_tables_to_appear_in_same_query!( + conversations, + participants, + conversation_participants +); diesel::joinable!(conversation_messages -> conversations (conversation_id)); diesel::joinable!(conversation_messages -> messages (message_id)); -diesel::allow_tables_to_appear_in_same_query!(conversations, messages, conversation_messages); \ No newline at end of file +diesel::allow_tables_to_appear_in_same_query!(conversations, messages, conversation_messages); diff --git a/kordophone-db/src/settings.rs b/kordophone-db/src/settings.rs index 4c14dcb..eb4367c 100644 --- a/kordophone-db/src/settings.rs +++ b/kordophone-db/src/settings.rs @@ -1,6 +1,6 @@ -use diesel::*; -use serde::{Serialize, de::DeserializeOwned}; use anyhow::Result; +use diesel::*; +use serde::{de::DeserializeOwned, Serialize}; #[derive(Insertable, Queryable, AsChangeset)] #[diesel(table_name = crate::schema::settings)] @@ -18,16 +18,15 @@ impl<'a> Settings<'a> { Self { connection } } - pub fn put( - &mut self, - k: &str, - v: &T, - ) -> Result<()> { + pub fn put(&mut self, k: &str, v: &T) -> Result<()> { use crate::schema::settings::dsl::*; let bytes = bincode::serialize(v)?; diesel::insert_into(settings) - .values(SettingsRow { key: k, value: &bytes }) + .values(SettingsRow { + key: k, + value: &bytes, + }) .on_conflict(key) .do_update() .set(value.eq(&bytes)) @@ -36,10 +35,7 @@ impl<'a> Settings<'a> { Ok(()) } - pub fn get( - &mut self, - k: &str, - ) -> Result> { + pub fn get(&mut self, k: &str) -> Result> { use crate::schema::settings::dsl::*; let blob: Option> = settings .select(value) @@ -49,7 +45,7 @@ impl<'a> Settings<'a> { Ok(match blob { Some(b) => Some(bincode::deserialize(&b)?), - None => None, + None => None, }) } @@ -60,12 +56,8 @@ impl<'a> Settings<'a> { pub fn list_keys(&mut self) -> Result> { use crate::schema::settings::dsl::*; - let keys: Vec = settings - .select(key) - .load(self.connection)?; - + let keys: Vec = settings.select(key).load(self.connection)?; + Ok(keys) } } - - diff --git a/kordophone-db/src/tests/mod.rs b/kordophone-db/src/tests/mod.rs index c8c64d3..1ab6e86 100644 --- a/kordophone-db/src/tests/mod.rs +++ b/kordophone-db/src/tests/mod.rs @@ -1,9 +1,9 @@ use crate::{ - database::{Database, DatabaseAccess}, + database::{Database, DatabaseAccess}, models::{ conversation::{Conversation, ConversationBuilder}, - participant::Participant, message::Message, + participant::Participant, }, }; @@ -11,9 +11,17 @@ use crate::{ fn participants_equal_ignoring_id(a: &Participant, b: &Participant) -> bool { match (a, b) { (Participant::Me, Participant::Me) => true, - (Participant::Remote { display_name: name_a, .. }, - Participant::Remote { display_name: name_b, .. }) => name_a == name_b, - _ => false + ( + Participant::Remote { + display_name: name_a, + .. + }, + Participant::Remote { + display_name: name_b, + .. + }, + ) => name_a == name_b, + _ => false, } } @@ -21,7 +29,9 @@ fn participants_vec_equal_ignoring_id(a: &[Participant], b: &[Participant]) -> b if a.len() != b.len() { return false; } - a.iter().zip(b.iter()).all(|(a, b)| participants_equal_ignoring_id(a, b)) + a.iter() + .zip(b.iter()) + .all(|(a, b)| participants_equal_ignoring_id(a, b)) } #[tokio::test] @@ -40,27 +50,33 @@ async fn test_add_conversation() { .display_name("Test Conversation") .build(); - repository.insert_conversation(test_conversation.clone()).unwrap(); + repository + .insert_conversation(test_conversation.clone()) + .unwrap(); - // Try to fetch with id now + // Try to fetch with id now let conversation = repository.get_conversation_by_guid(guid).unwrap().unwrap(); assert_eq!(conversation.guid, "test"); // Modify the conversation and update it - let modified_conversation = test_conversation.into_builder() + let modified_conversation = test_conversation + .into_builder() .display_name("Modified Conversation") .build(); - repository.insert_conversation(modified_conversation.clone()).unwrap(); + repository + .insert_conversation(modified_conversation.clone()) + .unwrap(); - // Make sure we still only have one conversation. + // Make sure we still only have one conversation. let all_conversations = repository.all_conversations(i32::MAX, 0).unwrap(); assert_eq!(all_conversations.len(), 1); - // And make sure the display name was updated + // And make sure the display name was updated let conversation = repository.get_conversation_by_guid(guid).unwrap().unwrap(); assert_eq!(conversation.display_name.unwrap(), "Modified Conversation"); - }).await; + }) + .await; } #[tokio::test] @@ -81,7 +97,10 @@ async fn test_conversation_participants() { let read_conversation = repository.get_conversation_by_guid(&guid).unwrap().unwrap(); let read_participants = read_conversation.participants; - assert!(participants_vec_equal_ignoring_id(&participants, &read_participants)); + assert!(participants_vec_equal_ignoring_id( + &participants, + &read_participants + )); // Try making another conversation with the same participants let conversation = ConversationBuilder::new() @@ -94,8 +113,12 @@ async fn test_conversation_participants() { let read_conversation = repository.get_conversation_by_guid(&guid).unwrap().unwrap(); let read_participants: Vec = read_conversation.participants; - assert!(participants_vec_equal_ignoring_id(&participants, &read_participants)); - }).await; + assert!(participants_vec_equal_ignoring_id( + &participants, + &read_participants + )); + }) + .await; } #[tokio::test] @@ -132,9 +155,16 @@ async fn test_all_conversations_with_participants() { let conv1 = all_conversations.iter().find(|c| c.guid == guid1).unwrap(); let conv2 = all_conversations.iter().find(|c| c.guid == guid2).unwrap(); - assert!(participants_vec_equal_ignoring_id(&conv1.participants, &participants1)); - assert!(participants_vec_equal_ignoring_id(&conv2.participants, &participants2)); - }).await; + assert!(participants_vec_equal_ignoring_id( + &conv1.participants, + &participants1 + )); + assert!(participants_vec_equal_ignoring_id( + &conv2.participants, + &participants2 + )); + }) + .await; } #[tokio::test] @@ -163,11 +193,17 @@ async fn test_messages() { .build(); // Insert both messages - repository.insert_message(&conversation_id, message1.clone()).unwrap(); - repository.insert_message(&conversation_id, message2.clone()).unwrap(); + repository + .insert_message(&conversation_id, message1.clone()) + .unwrap(); + repository + .insert_message(&conversation_id, message2.clone()) + .unwrap(); // Retrieve messages - let messages = repository.get_messages_for_conversation(&conversation_id).unwrap(); + let messages = repository + .get_messages_for_conversation(&conversation_id) + .unwrap(); assert_eq!(messages.len(), 2); // Verify first message (from Me) @@ -181,9 +217,13 @@ async fn test_messages() { if let Participant::Remote { display_name, .. } = &retrieved_message2.sender { assert_eq!(display_name, "Alice"); } else { - panic!("Expected Remote participant. Got: {:?}", retrieved_message2.sender); + panic!( + "Expected Remote participant. Got: {:?}", + retrieved_message2.sender + ); } - }).await; + }) + .await; } #[tokio::test] @@ -191,9 +231,7 @@ async fn test_message_ordering() { let mut db = Database::new_in_memory().unwrap(); db.with_repository(|repository| { // Create a conversation - let conversation = ConversationBuilder::new() - .display_name("Test Chat") - .build(); + let conversation = ConversationBuilder::new().display_name("Test Chat").build(); let conversation_id = conversation.guid.clone(); repository.insert_conversation(conversation).unwrap(); @@ -215,19 +253,28 @@ async fn test_message_ordering() { .build(); // Insert messages - repository.insert_message(&conversation_id, message1).unwrap(); - repository.insert_message(&conversation_id, message2).unwrap(); - repository.insert_message(&conversation_id, message3).unwrap(); + repository + .insert_message(&conversation_id, message1) + .unwrap(); + repository + .insert_message(&conversation_id, message2) + .unwrap(); + repository + .insert_message(&conversation_id, message3) + .unwrap(); // Retrieve messages and verify order - let messages = repository.get_messages_for_conversation(&conversation_id).unwrap(); + let messages = repository + .get_messages_for_conversation(&conversation_id) + .unwrap(); assert_eq!(messages.len(), 3); // Messages should be ordered by date for i in 1..messages.len() { - assert!(messages[i].date > messages[i-1].date); + assert!(messages[i].date > messages[i - 1].date); } - }).await; + }) + .await; } #[tokio::test] @@ -245,10 +292,7 @@ async fn test_insert_messages_batch() { // Prepare a batch of messages with increasing timestamps let now = chrono::Utc::now().naive_utc(); - let message1 = Message::builder() - .text("Hi".to_string()) - .date(now) - .build(); + let message1 = Message::builder().text("Hi".to_string()).date(now).build(); let message2 = Message::builder() .text("Hello".to_string()) @@ -280,7 +324,9 @@ async fn test_insert_messages_batch() { .unwrap(); // Retrieve messages and verify - let retrieved_messages = repository.get_messages_for_conversation(&conversation_id).unwrap(); + let retrieved_messages = repository + .get_messages_for_conversation(&conversation_id) + .unwrap(); assert_eq!(retrieved_messages.len(), original_messages.len()); // Ensure ordering by date @@ -299,8 +345,14 @@ async fn test_insert_messages_batch() { match (&original.sender, &retrieved.sender) { (Participant::Me, Participant::Me) => {} ( - Participant::Remote { display_name: o_name, .. }, - Participant::Remote { display_name: r_name, .. }, + Participant::Remote { + display_name: o_name, + .. + }, + Participant::Remote { + display_name: r_name, + .. + }, ) => assert_eq!(o_name, r_name), _ => panic!( "Sender mismatch: original {:?}, retrieved {:?}", @@ -310,7 +362,10 @@ async fn test_insert_messages_batch() { } // Make sure the last message is the last one we inserted - let last_message = repository.get_last_message_for_conversation(&conversation_id).unwrap().unwrap(); + let last_message = repository + .get_last_message_for_conversation(&conversation_id) + .unwrap() + .unwrap(); assert_eq!(last_message.id, message4.id); }) .await; @@ -329,7 +384,7 @@ async fn test_settings() { let keys = settings.list_keys().unwrap(); assert_eq!(keys.len(), 0); - // Try encoding a struct + // Try encoding a struct #[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Eq)] struct TestStruct { name: String, @@ -342,13 +397,34 @@ async fn test_settings() { }; settings.put("test_struct", &test_struct).unwrap(); - assert_eq!(settings.get::("test_struct").unwrap().unwrap(), test_struct); + assert_eq!( + settings.get::("test_struct").unwrap().unwrap(), + test_struct + ); // Test with an option - settings.put("test_struct_option", &Option::::None).unwrap(); - assert!(settings.get::>("test_struct_option").unwrap().unwrap().is_none()); + settings + .put("test_struct_option", &Option::::None) + .unwrap(); + assert!(settings + .get::>("test_struct_option") + .unwrap() + .unwrap() + .is_none()); - settings.put("test_struct_option", &Option::::Some("test".to_string())).unwrap(); - assert_eq!(settings.get::>("test_struct_option").unwrap().unwrap(), Some("test".to_string())); - }).await; + settings + .put( + "test_struct_option", + &Option::::Some("test".to_string()), + ) + .unwrap(); + assert_eq!( + settings + .get::>("test_struct_option") + .unwrap() + .unwrap(), + Some("test".to_string()) + ); + }) + .await; } diff --git a/kordophone/src/api/auth.rs b/kordophone/src/api/auth.rs index 4d8f65c..6e34cfc 100644 --- a/kordophone/src/api/auth.rs +++ b/kordophone/src/api/auth.rs @@ -22,7 +22,7 @@ impl Default for InMemoryAuthenticationStore { impl InMemoryAuthenticationStore { pub fn new(credentials: Option) -> Self { - Self { + Self { credentials, token: None, } diff --git a/kordophone/src/api/event_socket.rs b/kordophone/src/api/event_socket.rs index 8896c3b..636677d 100644 --- a/kordophone/src/api/event_socket.rs +++ b/kordophone/src/api/event_socket.rs @@ -1,6 +1,6 @@ -use async_trait::async_trait; -use crate::model::update::UpdateItem; use crate::model::event::Event; +use crate::model::update::UpdateItem; +use async_trait::async_trait; use futures_util::stream::Stream; #[async_trait] @@ -8,10 +8,10 @@ pub trait EventSocket { type Error; type EventStream: Stream>; type UpdateStream: Stream, Self::Error>>; - - /// Modern event pipeline + + /// Modern event pipeline async fn events(self) -> Self::EventStream; - /// Raw update items from the v1 API. + /// Raw update items from the v1 API. async fn raw_updates(self) -> Self::UpdateStream; -} \ No newline at end of file +} diff --git a/kordophone/src/lib.rs b/kordophone/src/lib.rs index f93f108..8688da3 100644 --- a/kordophone/src/lib.rs +++ b/kordophone/src/lib.rs @@ -4,4 +4,4 @@ pub mod model; pub use self::api::APIInterface; #[cfg(test)] -pub mod tests; \ No newline at end of file +pub mod tests; diff --git a/kordophone/src/model/conversation.rs b/kordophone/src/model/conversation.rs index 004b362..fa397ea 100644 --- a/kordophone/src/model/conversation.rs +++ b/kordophone/src/model/conversation.rs @@ -41,7 +41,7 @@ impl Identifiable for Conversation { fn id(&self) -> &Self::ID { &self.guid - } + } } #[derive(Default)] @@ -85,7 +85,10 @@ impl ConversationBuilder { self } - pub fn display_name(mut self, display_name: T) -> Self where T: Into { + pub fn display_name(mut self, display_name: T) -> Self + where + T: Into, + { self.display_name = Some(display_name.into()); self } diff --git a/kordophone/src/model/event.rs b/kordophone/src/model/event.rs index f44e4c7..2471559 100644 --- a/kordophone/src/model/event.rs +++ b/kordophone/src/model/event.rs @@ -15,13 +15,25 @@ pub enum EventData { impl From for Event { fn from(update: UpdateItem) -> Self { match update { - UpdateItem { conversation: Some(conversation), message: None, .. } - => Event { data: EventData::ConversationChanged(conversation), update_seq: update.seq }, + UpdateItem { + conversation: Some(conversation), + message: None, + .. + } => Event { + data: EventData::ConversationChanged(conversation), + update_seq: update.seq, + }, + + UpdateItem { + conversation: Some(conversation), + message: Some(message), + .. + } => Event { + data: EventData::MessageReceived(conversation, message), + update_seq: update.seq, + }, - UpdateItem { conversation: Some(conversation), message: Some(message), .. } - => Event { data: EventData::MessageReceived(conversation, message), update_seq: update.seq }, - _ => panic!("Invalid update item: {:?}", update), } } -} \ No newline at end of file +} diff --git a/kordophone/src/model/jwt.rs b/kordophone/src/model/jwt.rs index f458b5f..49d27b9 100644 --- a/kordophone/src/model/jwt.rs +++ b/kordophone/src/model/jwt.rs @@ -37,14 +37,14 @@ where D: serde::Deserializer<'de>, { use serde::de::Error; - + #[derive(Deserialize)] #[serde(untagged)] enum ExpValue { String(String), Number(i64), } - + match ExpValue::deserialize(deserializer)? { ExpValue::String(s) => s.parse().map_err(D::Error::custom), ExpValue::Number(n) => Ok(n), @@ -82,7 +82,9 @@ impl JwtToken { let payload: JwtPayload = serde_json::from_slice(&payload)?; // Parse jwt expiration date - let timestamp = DateTime::from_timestamp(payload.exp, 0).unwrap().naive_utc(); + let timestamp = DateTime::from_timestamp(payload.exp, 0) + .unwrap() + .naive_utc(); let expiration_date = DateTime::from_naive_utc_and_offset(timestamp, Utc); Ok(JwtToken { @@ -99,7 +101,7 @@ impl JwtToken { // try both encodings here. log::debug!("Attempting to decode JWT token: {}", token); - + let result = Self::decode_token_using_engine(token, general_purpose::STANDARD).or( Self::decode_token_using_engine(token, general_purpose::URL_SAFE_NO_PAD), ); diff --git a/kordophone/src/model/message.rs b/kordophone/src/model/message.rs index ef90efc..cca26f5 100644 --- a/kordophone/src/model/message.rs +++ b/kordophone/src/model/message.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use time::OffsetDateTime; +use time::OffsetDateTime; use uuid::Uuid; use super::Identifiable; @@ -12,7 +12,7 @@ pub struct AttributionInfo { /// Picture width #[serde(rename = "pgensh")] pub width: Option, - + /// Picture height #[serde(rename = "pgensw")] pub height: Option, @@ -26,7 +26,7 @@ pub struct AttachmentMetadata { #[derive(Debug, Clone, Deserialize)] pub struct Message { - pub guid: String, + pub guid: String, #[serde(rename = "text")] pub text: String, @@ -62,9 +62,9 @@ impl Identifiable for Message { #[derive(Default)] pub struct MessageBuilder { - guid: Option, - text: Option, - sender: Option, + guid: Option, + text: Option, + sender: Option, date: Option, file_transfer_guids: Option>, attachment_metadata: Option>, @@ -77,17 +77,17 @@ impl MessageBuilder { pub fn guid(mut self, guid: String) -> Self { self.guid = Some(guid); - self + self } pub fn text(mut self, text: String) -> Self { self.text = Some(text); - self + self } pub fn sender(mut self, sender: String) -> Self { self.sender = Some(sender); - self + self } pub fn date(mut self, date: OffsetDateTime) -> Self { @@ -100,7 +100,10 @@ impl MessageBuilder { self } - pub fn attachment_metadata(mut self, attachment_metadata: HashMap) -> Self { + pub fn attachment_metadata( + mut self, + attachment_metadata: HashMap, + ) -> Self { self.attachment_metadata = Some(attachment_metadata); self } @@ -116,4 +119,3 @@ impl MessageBuilder { } } } - diff --git a/kordophone/src/model/mod.rs b/kordophone/src/model/mod.rs index cc659aa..70538c7 100644 --- a/kordophone/src/model/mod.rs +++ b/kordophone/src/model/mod.rs @@ -23,4 +23,4 @@ pub use jwt::JwtToken; pub trait Identifiable { type ID; fn id(&self) -> &Self::ID; -} \ No newline at end of file +} diff --git a/kordophone/src/model/outgoing_message.rs b/kordophone/src/model/outgoing_message.rs index d3a4123..15e03a7 100644 --- a/kordophone/src/model/outgoing_message.rs +++ b/kordophone/src/model/outgoing_message.rs @@ -1,6 +1,6 @@ -use serde::Serialize; use super::conversation::ConversationID; use chrono::NaiveDateTime; +use serde::Serialize; use uuid::Uuid; #[derive(Debug, Clone, Serialize)] @@ -61,12 +61,12 @@ impl OutgoingMessageBuilder { } pub fn build(self) -> OutgoingMessage { - OutgoingMessage { + OutgoingMessage { guid: self.guid.unwrap_or_else(|| Uuid::new_v4()), - text: self.text.unwrap(), - conversation_id: self.conversation_id.unwrap(), + text: self.text.unwrap(), + conversation_id: self.conversation_id.unwrap(), file_transfer_guids: self.file_transfer_guids.unwrap_or_default(), date: chrono::Utc::now().naive_utc(), } } -} \ No newline at end of file +} diff --git a/kordophone/src/model/update.rs b/kordophone/src/model/update.rs index 4d89896..69889fa 100644 --- a/kordophone/src/model/update.rs +++ b/kordophone/src/model/update.rs @@ -1,6 +1,6 @@ -use serde::Deserialize; use super::conversation::Conversation; use super::message::Message; +use serde::Deserialize; #[derive(Debug, Clone, Deserialize)] pub struct UpdateItem { @@ -16,6 +16,10 @@ pub struct UpdateItem { impl Default for UpdateItem { fn default() -> Self { - Self { seq: 0, conversation: None, message: None } + Self { + seq: 0, + conversation: None, + message: None, + } } -} \ No newline at end of file +} diff --git a/kordophone/src/tests/mod.rs b/kordophone/src/tests/mod.rs index 43f21ef..ee8b46e 100644 --- a/kordophone/src/tests/mod.rs +++ b/kordophone/src/tests/mod.rs @@ -6,7 +6,7 @@ pub mod api_interface { use crate::model::Conversation; use super::*; - + #[tokio::test] async fn test_version() { let mut client = TestClient::new(); @@ -28,4 +28,4 @@ pub mod api_interface { assert_eq!(conversations.len(), 1); assert_eq!(conversations[0].display_name, test_convo.display_name); } -} \ No newline at end of file +} diff --git a/kordophone/src/tests/test_client.rs b/kordophone/src/tests/test_client.rs index a8ca377..c051e1f 100644 --- a/kordophone/src/tests/test_client.rs +++ b/kordophone/src/tests/test_client.rs @@ -14,9 +14,9 @@ use crate::{ }, }; +use bytes::Bytes; use futures_util::stream::BoxStream; use futures_util::StreamExt; -use bytes::Bytes; pub struct TestClient { pub version: &'static str, @@ -120,7 +120,11 @@ impl APIInterface for TestClient { Ok(TestEventSocket::new()) } - async fn fetch_attachment_data(&mut self, guid: &String, preview: bool) -> Result { + async fn fetch_attachment_data( + &mut self, + guid: &String, + preview: bool, + ) -> Result { Ok(futures_util::stream::iter(vec![Ok(Bytes::from_static(b"test"))]).boxed()) } } diff --git a/kordophoned/build.rs b/kordophoned/build.rs index e9d606b..ec36f2c 100644 --- a/kordophoned/build.rs +++ b/kordophoned/build.rs @@ -11,14 +11,12 @@ fn main() { ..Default::default() }; - let xml = std::fs::read_to_string(KORDOPHONE_XML) - .expect("Error reading server dbus interface"); + let xml = std::fs::read_to_string(KORDOPHONE_XML).expect("Error reading server dbus interface"); - let output = dbus_codegen::generate(&xml, &opts) - .expect("Error generating server dbus interface"); + let output = + dbus_codegen::generate(&xml, &opts).expect("Error generating server dbus interface"); - std::fs::write(out_path, output) - .expect("Error writing server dbus code"); + std::fs::write(out_path, output).expect("Error writing server dbus code"); println!("cargo:rerun-if-changed={}", KORDOPHONE_XML); } diff --git a/kordophoned/src/daemon/attachment_store.rs b/kordophoned/src/daemon/attachment_store.rs index 6395ea0..7b35cb9 100644 --- a/kordophoned/src/daemon/attachment_store.rs +++ b/kordophoned/src/daemon/attachment_store.rs @@ -16,8 +16,8 @@ use crate::daemon::models::Attachment; use crate::daemon::Daemon; use std::sync::Arc; -use tokio::sync::Mutex; use tokio::sync::mpsc::{Receiver, Sender}; +use tokio::sync::Mutex; use tokio::pin; @@ -32,7 +32,7 @@ pub enum AttachmentStoreEvent { GetAttachmentInfo(String, Reply), // Queue a download for a given attachment guid. - // Args: + // Args: // - attachment guid // - preview: whether to download the preview (true) or full attachment (false) QueueDownloadAttachment(String, bool), @@ -62,7 +62,10 @@ impl AttachmentStore { data_dir.join("attachments") } - pub fn new(database: Arc>, daemon_event_sink: Sender) -> AttachmentStore { + pub fn new( + database: Arc>, + daemon_event_sink: Sender, + ) -> AttachmentStore { let store_path = Self::get_default_store_path(); log::info!(target: target::ATTACHMENTS, "Attachment store path: {}", store_path.display()); @@ -97,7 +100,7 @@ impl AttachmentStore { metadata: None, } } - + async fn download_attachment(&mut self, attachment: &Attachment, preview: bool) -> Result<()> { if attachment.is_downloaded(preview) { log::info!(target: target::ATTACHMENTS, "Attachment already downloaded: {}", attachment.guid); @@ -130,7 +133,7 @@ impl AttachmentStore { log::info!(target: target::ATTACHMENTS, "Completed download for attachment: {}", attachment.guid); Ok(()) } - + pub async fn run(&mut self) { loop { tokio::select! { diff --git a/kordophoned/src/daemon/mod.rs b/kordophoned/src/daemon/mod.rs index 3f64f3c..d62b67c 100644 --- a/kordophoned/src/daemon/mod.rs +++ b/kordophoned/src/daemon/mod.rs @@ -150,7 +150,8 @@ impl Daemon { } // Attachment store - let mut attachment_store = AttachmentStore::new(self.database.clone(), self.event_sender.clone()); + let mut attachment_store = + AttachmentStore::new(self.database.clone(), self.event_sender.clone()); self.attachment_store_sink = Some(attachment_store.get_event_sink()); tokio::spawn(async move { attachment_store.run().await; @@ -304,7 +305,10 @@ impl Daemon { self.attachment_store_sink .as_ref() .unwrap() - .send(AttachmentStoreEvent::QueueDownloadAttachment(attachment_id, preview)) + .send(AttachmentStoreEvent::QueueDownloadAttachment( + attachment_id, + preview, + )) .await .unwrap(); diff --git a/kordophoned/src/daemon/models/attachment.rs b/kordophoned/src/daemon/models/attachment.rs index 51ab0c9..0251451 100644 --- a/kordophoned/src/daemon/models/attachment.rs +++ b/kordophoned/src/daemon/models/attachment.rs @@ -20,14 +20,20 @@ pub struct Attachment { impl Attachment { pub fn get_path(&self, preview: bool) -> PathBuf { - self.base_path.with_extension(if preview { "preview" } else { "full" }) + self.base_path + .with_extension(if preview { "preview" } else { "full" }) } pub fn is_downloaded(&self, preview: bool) -> bool { - std::fs::exists(&self.get_path(preview)) - .expect(format!("Wasn't able to check for the existence of an attachment file path at {}", &self.get_path(preview).display()).as_str()) + std::fs::exists(&self.get_path(preview)).expect( + format!( + "Wasn't able to check for the existence of an attachment file path at {}", + &self.get_path(preview).display() + ) + .as_str(), + ) } -} +} impl From for AttachmentMetadata { fn from(metadata: kordophone::model::message::AttachmentMetadata) -> Self { @@ -61,4 +67,4 @@ impl From for kordophone::model::message::AttributionInfo { height: info.height, } } -} \ No newline at end of file +} diff --git a/kordophoned/src/daemon/models/message.rs b/kordophoned/src/daemon/models/message.rs index 4735275..f1bc32f 100644 --- a/kordophoned/src/daemon/models/message.rs +++ b/kordophoned/src/daemon/models/message.rs @@ -1,11 +1,11 @@ -use chrono::NaiveDateTime; use chrono::DateTime; +use chrono::NaiveDateTime; -use std::collections::HashMap; +use crate::daemon::attachment_store::AttachmentStore; +use crate::daemon::models::Attachment; use kordophone::model::message::AttachmentMetadata; use kordophone::model::outgoing_message::OutgoingMessage; -use crate::daemon::models::Attachment; -use crate::daemon::attachment_store::AttachmentStore; +use std::collections::HashMap; #[derive(Clone, Debug)] pub enum Participant { @@ -54,7 +54,7 @@ impl Participant { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug)] pub struct Message { pub id: String, pub sender: Participant, @@ -63,24 +63,34 @@ pub struct Message { pub attachments: Vec, } -fn attachments_from(file_transfer_guids: &Vec, attachment_metadata: &Option>) -> Vec { +fn attachments_from( + file_transfer_guids: &Vec, + attachment_metadata: &Option>, +) -> Vec { file_transfer_guids .iter() .map(|guid| { - let mut attachment = AttachmentStore::get_attachment_impl(&AttachmentStore::get_default_store_path(), guid); + let mut attachment = AttachmentStore::get_attachment_impl( + &AttachmentStore::get_default_store_path(), + guid, + ); attachment.metadata = match attachment_metadata { - Some(attachment_metadata) => attachment_metadata.get(guid).cloned().map(|metadata| metadata.into()), + Some(attachment_metadata) => attachment_metadata + .get(guid) + .cloned() + .map(|metadata| metadata.into()), None => None, }; attachment }) .collect() -} +} impl From for Message { fn from(message: kordophone_db::models::Message) -> Self { - let attachments = attachments_from(&message.file_transfer_guids, &message.attachment_metadata); + let attachments = + attachments_from(&message.file_transfer_guids, &message.attachment_metadata); Self { id: message.id, sender: message.sender.into(), @@ -105,11 +115,21 @@ impl From for kordophone_db::models::Message { date: message.date, file_transfer_guids: message.attachments.iter().map(|a| a.guid.clone()).collect(), attachment_metadata: { - let metadata_map: HashMap = message.attachments - .iter() - .filter_map(|a| a.metadata.as_ref().map(|m| (a.guid.clone(), m.clone().into()))) - .collect(); - if metadata_map.is_empty() { None } else { Some(metadata_map) } + let metadata_map: HashMap = + message + .attachments + .iter() + .filter_map(|a| { + a.metadata + .as_ref() + .map(|m| (a.guid.clone(), m.clone().into())) + }) + .collect(); + if metadata_map.is_empty() { + None + } else { + Some(metadata_map) + } }, } } @@ -117,7 +137,8 @@ impl From for kordophone_db::models::Message { impl From for Message { fn from(message: kordophone::model::Message) -> Self { - let attachments = attachments_from(&message.file_transfer_guids, &message.attachment_metadata); + let attachments = + attachments_from(&message.file_transfer_guids, &message.attachment_metadata); Self { id: message.guid, sender: match message.sender { @@ -130,12 +151,10 @@ impl From for Message { text: message.text, date: DateTime::from_timestamp( message.date.unix_timestamp(), - message.date.unix_timestamp_nanos() - .try_into() - .unwrap_or(0), - ) - .unwrap() - .naive_local(), + message.date.unix_timestamp_nanos().try_into().unwrap_or(0), + ) + .unwrap() + .naive_local(), attachments, } } diff --git a/kordophoned/src/daemon/models/mod.rs b/kordophoned/src/daemon/models/mod.rs index 8a63c4a..9c8ba1c 100644 --- a/kordophoned/src/daemon/models/mod.rs +++ b/kordophoned/src/daemon/models/mod.rs @@ -2,4 +2,4 @@ pub mod attachment; pub mod message; pub use attachment::Attachment; -pub use message::Message; \ No newline at end of file +pub use message::Message; diff --git a/kordophoned/src/daemon/post_office.rs b/kordophoned/src/daemon/post_office.rs index 55d626e..60c52ae 100644 --- a/kordophoned/src/daemon/post_office.rs +++ b/kordophoned/src/daemon/post_office.rs @@ -1,13 +1,13 @@ use std::collections::VecDeque; use std::time::Duration; -use tokio::sync::mpsc::{Sender, Receiver}; +use tokio::sync::mpsc::{Receiver, Sender}; use tokio::sync::Mutex; use tokio_condvar::Condvar; use crate::daemon::events::Event as DaemonEvent; -use kordophone::model::outgoing_message::OutgoingMessage; use kordophone::api::APIInterface; +use kordophone::model::outgoing_message::OutgoingMessage; use anyhow::Result; @@ -25,15 +25,19 @@ pub struct PostOffice Result> { event_sink: Sender, make_client: F, message_queue: Mutex>, - message_available: Condvar, + message_available: Condvar, } impl Result> PostOffice { - pub fn new(event_source: Receiver, event_sink: Sender, make_client: F) -> Self { - Self { + pub fn new( + event_source: Receiver, + event_sink: Sender, + make_client: F, + ) -> Self { + Self { event_source, - event_sink, - make_client, + event_sink, + make_client, message_queue: Mutex::new(VecDeque::new()), message_available: Condvar::new(), } @@ -85,13 +89,12 @@ impl Result> PostOffice { } async fn try_send_message( - make_client: &mut F, - event_sink: &Sender, - message: OutgoingMessage - ) -> Vec - { + make_client: &mut F, + event_sink: &Sender, + message: OutgoingMessage, + ) -> Vec { let mut retry_messages = Vec::new(); - + match (make_client)().await { Ok(mut client) => { log::debug!(target: target::POST_OFFICE, "Obtained client, sending message."); @@ -100,7 +103,8 @@ impl Result> PostOffice { log::info!(target: target::POST_OFFICE, "Message sent successfully: {}", message.guid); let conversation_id = message.conversation_id.clone(); - let event = DaemonEvent::MessageSent(sent_message.into(), message, conversation_id); + let event = + DaemonEvent::MessageSent(sent_message.into(), message, conversation_id); event_sink.send(event).await.unwrap(); } @@ -123,4 +127,4 @@ impl Result> PostOffice { retry_messages } -} \ No newline at end of file +} diff --git a/kordophoned/src/daemon/settings.rs b/kordophoned/src/daemon/settings.rs index 265c988..3c145f4 100644 --- a/kordophoned/src/daemon/settings.rs +++ b/kordophoned/src/daemon/settings.rs @@ -1,5 +1,5 @@ -use kordophone_db::settings::Settings as DbSettings; use anyhow::Result; +use kordophone_db::settings::Settings as DbSettings; pub mod keys { pub static SERVER_URL: &str = "ServerURL"; @@ -7,8 +7,7 @@ pub mod keys { pub static TOKEN: &str = "Token"; } -#[derive(Debug)] -#[derive(Default)] +#[derive(Debug, Default)] pub struct Settings { pub server_url: Option, pub username: Option, @@ -20,7 +19,7 @@ impl Settings { let server_url = db_settings.get(keys::SERVER_URL)?; let username = db_settings.get(keys::USERNAME)?; let token = db_settings.get(keys::TOKEN)?; - + // Create the settings struct with the results let settings = Self { server_url, @@ -30,7 +29,7 @@ impl Settings { // Load bearing log::debug!("Loaded settings: {:?}", settings); - + Ok(settings) } @@ -47,4 +46,3 @@ impl Settings { Ok(()) } } - diff --git a/kordophoned/src/daemon/update_monitor.rs b/kordophoned/src/daemon/update_monitor.rs index 8447619..184832a 100644 --- a/kordophoned/src/daemon/update_monitor.rs +++ b/kordophoned/src/daemon/update_monitor.rs @@ -1,24 +1,21 @@ use crate::daemon::{ - Daemon, - DaemonResult, - events::{Event, Reply}, - target, + target, Daemon, DaemonResult, }; -use kordophone::APIInterface; use kordophone::api::event_socket::EventSocket; use kordophone::model::event::Event as UpdateEvent; use kordophone::model::event::EventData as UpdateEventData; +use kordophone::APIInterface; use kordophone_db::database::Database; use kordophone_db::database::DatabaseAccess; -use tokio::sync::mpsc::Sender; -use std::sync::Arc; -use tokio::sync::Mutex; use std::collections::HashMap; +use std::sync::Arc; use std::time::{Duration, Instant}; +use tokio::sync::mpsc::Sender; +use tokio::sync::Mutex; pub struct UpdateMonitor { database: Arc>, @@ -29,8 +26,8 @@ pub struct UpdateMonitor { impl UpdateMonitor { pub fn new(database: Arc>, event_sender: Sender) -> Self { - Self { - database, + Self { + database, event_sender, last_sync_times: HashMap::new(), update_seq: None, @@ -42,23 +39,24 @@ impl UpdateMonitor { make_event: impl FnOnce(Reply) -> Event, ) -> DaemonResult { let (reply_tx, reply_rx) = tokio::sync::oneshot::channel(); - self.event_sender.send(make_event(reply_tx)) + self.event_sender + .send(make_event(reply_tx)) .await .map_err(|_| "Failed to send event")?; - + reply_rx.await.map_err(|_| "Failed to receive reply".into()) } - + async fn handle_update(&mut self, update: UpdateEvent) { self.update_seq = Some(update.update_seq); - + match update.data { UpdateEventData::ConversationChanged(conversation) => { log::info!(target: target::UPDATES, "Conversation changed: {:?}", conversation); // Check if we've synced this conversation recently (within 5 seconds) // This is currently a hack/workaround to prevent an infinite loop of sync events, because for some reason - // imagent will post a conversation changed notification when we call getMessages. + // imagent will post a conversation changed notification when we call getMessages. if let Some(last_sync) = self.last_sync_times.get(&conversation.guid) { if last_sync.elapsed() < Duration::from_secs(5) { log::info!(target: target::UPDATES, "Skipping sync for conversation id: {}. Last sync was {} seconds ago.", @@ -67,8 +65,12 @@ impl UpdateMonitor { } } - // This is the non-hacky path once we can reason about chat items with associatedMessageGUIDs (e.g., reactions). - let last_message = self.database.with_repository(|r| r.get_last_message_for_conversation(&conversation.guid)).await.unwrap_or_default(); + // This is the non-hacky path once we can reason about chat items with associatedMessageGUIDs (e.g., reactions). + let last_message = self + .database + .with_repository(|r| r.get_last_message_for_conversation(&conversation.guid)) + .await + .unwrap_or_default(); match (&last_message, &conversation.last_message) { (Some(message), Some(conversation_message)) => { if message.id == conversation_message.guid { @@ -80,10 +82,12 @@ impl UpdateMonitor { }; // Update the last sync time and proceed with sync - self.last_sync_times.insert(conversation.guid.clone(), Instant::now()); - + self.last_sync_times + .insert(conversation.guid.clone(), Instant::now()); + log::info!(target: target::UPDATES, "Syncing new messages for conversation id: {}", conversation.guid); - self.send_event(|r| Event::SyncConversation(conversation.guid, r)).await + self.send_event(|r| Event::SyncConversation(conversation.guid, r)) + .await .unwrap_or_else(|e| { log::error!("Failed to send daemon event: {}", e); }); @@ -92,14 +96,15 @@ impl UpdateMonitor { UpdateEventData::MessageReceived(conversation, message) => { log::info!(target: target::UPDATES, "Message received: msgid:{:?}, convid:{:?}", message.guid, conversation.guid); log::info!(target: target::UPDATES, "Triggering message sync for conversation id: {}", conversation.guid); - self.send_event(|r| Event::SyncConversation(conversation.guid, r)).await + self.send_event(|r| Event::SyncConversation(conversation.guid, r)) + .await .unwrap_or_else(|e| { log::error!("Failed to send daemon event: {}", e); }); } } } - + pub async fn run(&mut self) { use futures_util::stream::StreamExt; @@ -130,15 +135,15 @@ impl UpdateMonitor { log::debug!(target: target::UPDATES, "Starting event stream"); let mut event_stream = socket.events().await; - - // We won't know if the websocket is dead until we try to send a message, so time out waiting for - // a message every 30 seconds. + + // We won't know if the websocket is dead until we try to send a message, so time out waiting for + // a message every 30 seconds. let mut timeout = tokio::time::interval(Duration::from_secs(30)); timeout.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); // First tick will happen immediately timeout.tick().await; - + loop { tokio::select! { Some(result) = event_stream.next() => { @@ -161,9 +166,9 @@ impl UpdateMonitor { } } } - + // Add a small delay before reconnecting to avoid tight reconnection loops tokio::time::sleep(Duration::from_secs(1)).await; } } -} \ No newline at end of file +} diff --git a/kordophoned/src/dbus/endpoint.rs b/kordophoned/src/dbus/endpoint.rs index ec76030..aa327b5 100644 --- a/kordophoned/src/dbus/endpoint.rs +++ b/kordophoned/src/dbus/endpoint.rs @@ -42,11 +42,11 @@ impl DbusRegistry { R: IntoIterator>, { let dbus_path = String::from(path); - + let mut cr = self.crossroads.lock().unwrap(); let tokens: Vec<_> = register_fn(&mut cr).into_iter().collect(); cr.insert(dbus_path, &tokens, implementation); - + // Start message handler if not already started let mut handler_started = self.message_handler_started.lock().unwrap(); if !*handler_started { diff --git a/kordophoned/src/dbus/mod.rs b/kordophoned/src/dbus/mod.rs index 143fb83..17fbf1e 100644 --- a/kordophoned/src/dbus/mod.rs +++ b/kordophoned/src/dbus/mod.rs @@ -13,4 +13,4 @@ pub mod interface { pub use crate::interface::NetBuzzertKordophoneRepositoryConversationsUpdated as ConversationsUpdated; pub use crate::interface::NetBuzzertKordophoneRepositoryMessagesUpdated as MessagesUpdated; } -} \ No newline at end of file +} diff --git a/kordophoned/src/dbus/server_impl.rs b/kordophoned/src/dbus/server_impl.rs index 4053a61..ba5773e 100644 --- a/kordophoned/src/dbus/server_impl.rs +++ b/kordophoned/src/dbus/server_impl.rs @@ -7,7 +7,8 @@ use tokio::sync::oneshot; use crate::daemon::{ events::{Event, Reply}, - settings::Settings, DaemonResult, + settings::Settings, + DaemonResult, }; use crate::dbus::interface::NetBuzzertKordophoneRepository as DbusRepository; @@ -136,52 +137,82 @@ impl DbusRepository for ServerImpl { "sender".into(), arg::Variant(Box::new(msg.sender.display_name())), ); - + // Add attachments array - let attachments: Vec = msg.attachments + let attachments: Vec = msg + .attachments .into_iter() .map(|attachment| { let mut attachment_map = arg::PropMap::new(); - attachment_map.insert("guid".into(), arg::Variant(Box::new(attachment.guid.clone()))); - + attachment_map.insert( + "guid".into(), + arg::Variant(Box::new(attachment.guid.clone())), + ); + // Get attachment paths and download status let path = attachment.get_path(false); let preview_path = attachment.get_path(true); let downloaded = attachment.is_downloaded(false); let preview_downloaded = attachment.is_downloaded(true); - - attachment_map.insert("path".into(), arg::Variant(Box::new(path.to_string_lossy().to_string()))); - attachment_map.insert("preview_path".into(), arg::Variant(Box::new(preview_path.to_string_lossy().to_string()))); - attachment_map.insert("downloaded".into(), arg::Variant(Box::new(downloaded))); - attachment_map.insert("preview_downloaded".into(), arg::Variant(Box::new(preview_downloaded))); - + + attachment_map.insert( + "path".into(), + arg::Variant(Box::new(path.to_string_lossy().to_string())), + ); + attachment_map.insert( + "preview_path".into(), + arg::Variant(Box::new( + preview_path.to_string_lossy().to_string(), + )), + ); + attachment_map.insert( + "downloaded".into(), + arg::Variant(Box::new(downloaded)), + ); + attachment_map.insert( + "preview_downloaded".into(), + arg::Variant(Box::new(preview_downloaded)), + ); + // Add metadata if present if let Some(ref metadata) = attachment.metadata { let mut metadata_map = arg::PropMap::new(); - + // Add attribution_info if present if let Some(ref attribution_info) = metadata.attribution_info { let mut attribution_map = arg::PropMap::new(); - + if let Some(width) = attribution_info.width { - attribution_map.insert("width".into(), arg::Variant(Box::new(width as i32))); + attribution_map.insert( + "width".into(), + arg::Variant(Box::new(width as i32)), + ); } if let Some(height) = attribution_info.height { - attribution_map.insert("height".into(), arg::Variant(Box::new(height as i32))); + attribution_map.insert( + "height".into(), + arg::Variant(Box::new(height as i32)), + ); } - - metadata_map.insert("attribution_info".into(), arg::Variant(Box::new(attribution_map))); + + metadata_map.insert( + "attribution_info".into(), + arg::Variant(Box::new(attribution_map)), + ); } - - attachment_map.insert("metadata".into(), arg::Variant(Box::new(metadata_map))); + + attachment_map.insert( + "metadata".into(), + arg::Variant(Box::new(metadata_map)), + ); } - + attachment_map }) .collect(); - + map.insert("attachments".into(), arg::Variant(Box::new(attachments))); - + map }) .collect() @@ -216,20 +247,21 @@ impl DbusRepository for ServerImpl { ( // - path: string path.to_string_lossy().to_string(), - // - preview_path: string preview_path.to_string_lossy().to_string(), - // - downloaded: boolean downloaded, - // - preview_downloaded: boolean preview_downloaded, ) }) } - fn download_attachment(&mut self, attachment_id: String, preview: bool) -> Result<(), dbus::MethodErr> { + fn download_attachment( + &mut self, + attachment_id: String, + preview: bool, + ) -> Result<(), dbus::MethodErr> { // For now, just trigger the download event - we'll implement the actual download logic later self.send_event_sync(|r| Event::DownloadAttachment(attachment_id, preview, r)) } @@ -286,7 +318,6 @@ impl DbusSettings for ServerImpl { } } - fn run_sync_future(f: F) -> Result where T: Send, diff --git a/kordophoned/src/main.rs b/kordophoned/src/main.rs index c3454f4..629b26a 100644 --- a/kordophoned/src/main.rs +++ b/kordophoned/src/main.rs @@ -60,14 +60,12 @@ async fn main() { // Create and register server implementation let server = ServerImpl::new(daemon.event_sender.clone()); - dbus_registry.register_object( - interface::OBJECT_PATH, - server, - |cr| vec![ + dbus_registry.register_object(interface::OBJECT_PATH, server, |cr| { + vec![ interface::register_net_buzzert_kordophone_repository(cr), interface::register_net_buzzert_kordophone_settings(cr), ] - ); + }); let mut signal_receiver = daemon.obtain_signal_receiver(); tokio::spawn(async move { diff --git a/kpcli/build.rs b/kpcli/build.rs index 7d9bf4e..9254308 100644 --- a/kpcli/build.rs +++ b/kpcli/build.rs @@ -10,14 +10,12 @@ fn main() { ..Default::default() }; - let xml = std::fs::read_to_string(KORDOPHONE_XML) - .expect("Error reading server dbus interface"); + let xml = std::fs::read_to_string(KORDOPHONE_XML).expect("Error reading server dbus interface"); - let output = dbus_codegen::generate(&xml, &opts) - .expect("Error generating client dbus interface"); + let output = + dbus_codegen::generate(&xml, &opts).expect("Error generating client dbus interface"); - std::fs::write(out_path, output) - .expect("Error writing client dbus code"); + std::fs::write(out_path, output).expect("Error writing client dbus code"); println!("cargo:rerun-if-changed={}", KORDOPHONE_XML); -} +} diff --git a/kpcli/src/client/mod.rs b/kpcli/src/client/mod.rs index 7438868..edfece7 100644 --- a/kpcli/src/client/mod.rs +++ b/kpcli/src/client/mod.rs @@ -1,12 +1,12 @@ -use kordophone::APIInterface; -use kordophone::api::http_client::HTTPAPIClient; -use kordophone::api::http_client::Credentials; -use kordophone::api::InMemoryAuthenticationStore; use kordophone::api::event_socket::EventSocket; +use kordophone::api::http_client::Credentials; +use kordophone::api::http_client::HTTPAPIClient; +use kordophone::api::InMemoryAuthenticationStore; +use kordophone::APIInterface; +use crate::printers::{ConversationPrinter, MessagePrinter}; use anyhow::Result; use clap::Subcommand; -use crate::printers::{ConversationPrinter, MessagePrinter}; use kordophone::model::event::EventData; use kordophone::model::outgoing_message::OutgoingMessage; @@ -16,18 +16,18 @@ pub fn make_api_client_from_env() -> HTTPAPIClient dotenv::dotenv().ok(); // read from env - let base_url = std::env::var("KORDOPHONE_API_URL") - .expect("KORDOPHONE_API_URL must be set"); + let base_url = std::env::var("KORDOPHONE_API_URL").expect("KORDOPHONE_API_URL must be set"); let credentials = Credentials { - username: std::env::var("KORDOPHONE_USERNAME") - .expect("KORDOPHONE_USERNAME must be set"), + username: std::env::var("KORDOPHONE_USERNAME").expect("KORDOPHONE_USERNAME must be set"), - password: std::env::var("KORDOPHONE_PASSWORD") - .expect("KORDOPHONE_PASSWORD must be set"), + password: std::env::var("KORDOPHONE_PASSWORD").expect("KORDOPHONE_PASSWORD must be set"), }; - HTTPAPIClient::new(base_url.parse().unwrap(), InMemoryAuthenticationStore::new(Some(credentials))) + HTTPAPIClient::new( + base_url.parse().unwrap(), + InMemoryAuthenticationStore::new(Some(credentials)), + ) } #[derive(Subcommand)] @@ -36,9 +36,7 @@ pub enum Commands { Conversations, /// Prints all messages in a conversation. - Messages { - conversation_id: String, - }, + Messages { conversation_id: String }, /// Prints the server Kordophone version. Version, @@ -65,7 +63,10 @@ impl Commands { Commands::Messages { conversation_id } => client.print_messages(conversation_id).await, Commands::RawUpdates => client.print_raw_updates().await, Commands::Events => client.print_events().await, - Commands::SendMessage { conversation_id, message } => client.send_message(conversation_id, message).await, + Commands::SendMessage { + conversation_id, + message, + } => client.send_message(conversation_id, message).await, } } } @@ -96,7 +97,10 @@ impl ClientCli { } pub async fn print_messages(&mut self, conversation_id: String) -> Result<()> { - let messages = self.api.get_messages(&conversation_id, None, None, None).await?; + let messages = self + .api + .get_messages(&conversation_id, None, None, None) + .await?; for message in messages { println!("{}", MessagePrinter::new(&message.into())); } @@ -113,8 +117,11 @@ impl ClientCli { println!("Conversation changed: {}", conversation.guid); } EventData::MessageReceived(conversation, message) => { - println!("Message received: msg: {} conversation: {}", message.guid, conversation.guid); - } + println!( + "Message received: msg: {} conversation: {}", + message.guid, conversation.guid + ); + } } } Ok(()) @@ -143,5 +150,3 @@ impl ClientCli { Ok(()) } } - - diff --git a/kpcli/src/daemon/mod.rs b/kpcli/src/daemon/mod.rs index 00c7d88..5bd0f8f 100644 --- a/kpcli/src/daemon/mod.rs +++ b/kpcli/src/daemon/mod.rs @@ -1,8 +1,8 @@ +use crate::printers::{ConversationPrinter, MessagePrinter}; use anyhow::Result; use clap::Subcommand; use dbus::blocking::{Connection, Proxy}; use prettytable::table; -use crate::printers::{ConversationPrinter, MessagePrinter}; const DBUS_NAME: &str = "net.buzzert.kordophonecd"; const DBUS_PATH: &str = "/net/buzzert/kordophonecd/daemon"; @@ -21,9 +21,7 @@ pub enum Commands { Conversations, /// Runs a full sync operation for a conversation and its messages. - Sync { - conversation_id: Option, - }, + Sync { conversation_id: Option }, /// Runs a sync operation for the conversation list. SyncList, @@ -31,7 +29,7 @@ pub enum Commands { /// Prints the server Kordophone version. Version, - /// Configuration options + /// Configuration options Config { #[command(subcommand)] command: ConfigCommands, @@ -62,14 +60,10 @@ pub enum ConfigCommands { Print, /// Sets the server URL. - SetServerUrl { - url: String, - }, + SetServerUrl { url: String }, /// Sets the username. - SetUsername { - username: String, - }, + SetUsername { username: String }, } impl Commands { @@ -82,9 +76,19 @@ impl Commands { Commands::SyncList => client.sync_conversations_list().await, Commands::Config { command } => client.config(command).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::SendMessage { conversation_id, text } => client.enqueue_outgoing_message(conversation_id, text).await, + Commands::SendMessage { + conversation_id, + text, + } => client.enqueue_outgoing_message(conversation_id, text).await, } } } @@ -96,12 +100,13 @@ struct DaemonCli { impl DaemonCli { pub fn new() -> Result { Ok(Self { - conn: Connection::new_session()? + conn: Connection::new_session()?, }) } fn proxy(&self) -> Proxy<&Connection> { - self.conn.with_proxy(DBUS_NAME, DBUS_PATH, std::time::Duration::from_millis(5000)) + self.conn + .with_proxy(DBUS_NAME, DBUS_PATH, std::time::Duration::from_millis(5000)) } pub async fn print_version(&mut self) -> Result<()> { @@ -117,7 +122,7 @@ impl DaemonCli { for conversation in conversations { println!("{}", ConversationPrinter::new(&conversation.into())); } - + Ok(()) } @@ -136,8 +141,16 @@ impl DaemonCli { .map_err(|e| anyhow::anyhow!("Failed to sync conversations: {}", e)) } - pub async fn print_messages(&mut self, conversation_id: String, last_message_id: Option) -> Result<()> { - let messages = KordophoneRepository::get_messages(&self.proxy(), &conversation_id, &last_message_id.unwrap_or_default())?; + pub async fn print_messages( + &mut self, + conversation_id: String, + last_message_id: Option, + ) -> Result<()> { + let messages = KordophoneRepository::get_messages( + &self.proxy(), + &conversation_id, + &last_message_id.unwrap_or_default(), + )?; println!("Number of messages: {}", messages.len()); for message in messages { @@ -147,8 +160,13 @@ impl DaemonCli { 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)?; + 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(()) } @@ -159,10 +177,12 @@ impl DaemonCli { pub use super::dbus_interface::NetBuzzertKordophoneRepositoryConversationsUpdated as ConversationsUpdated; } - let _id = self.proxy().match_signal(|h: dbus_signals::ConversationsUpdated, _: &Connection, _: &Message| { - println!("Signal: Conversations updated"); - true - }); + let _id = self.proxy().match_signal( + |h: dbus_signals::ConversationsUpdated, _: &Connection, _: &Message| { + println!("Signal: Conversations updated"); + true + }, + ); println!("Waiting for signals..."); loop { @@ -205,4 +225,4 @@ impl DaemonCli { KordophoneRepository::delete_all_conversations(&self.proxy()) .map_err(|e| anyhow::anyhow!("Failed to delete all conversations: {}", e)) } -} \ No newline at end of file +} diff --git a/kpcli/src/db/mod.rs b/kpcli/src/db/mod.rs index e5dc513..ee80369 100644 --- a/kpcli/src/db/mod.rs +++ b/kpcli/src/db/mod.rs @@ -3,34 +3,37 @@ use clap::Subcommand; use kordophone::APIInterface; use std::{env, path::PathBuf}; +use crate::{ + client, + printers::{ConversationPrinter, MessagePrinter}, +}; use kordophone_db::database::{Database, DatabaseAccess}; -use crate::{client, printers::{ConversationPrinter, MessagePrinter}}; #[derive(Subcommand)] pub enum Commands { /// For dealing with the table of cached conversations. Conversations { #[clap(subcommand)] - command: ConversationCommands + command: ConversationCommands, }, /// For dealing with the table of cached messages. Messages { #[clap(subcommand)] - command: MessageCommands + command: MessageCommands, }, /// For managing settings in the database. Settings { #[clap(subcommand)] - command: SettingsCommands + command: SettingsCommands, }, } #[derive(Subcommand)] pub enum ConversationCommands { /// Lists all conversations currently in the database. - List, + List, /// Syncs with an API client. Sync, @@ -39,9 +42,7 @@ pub enum ConversationCommands { #[derive(Subcommand)] pub enum MessageCommands { /// Prints all messages in a conversation. - List { - conversation_id: String - }, + List { conversation_id: String }, } #[derive(Subcommand)] @@ -49,7 +50,7 @@ pub enum SettingsCommands { /// Lists all settings or gets a specific setting. Get { /// The key to get. If not provided, all settings will be listed. - key: Option + key: Option, }, /// Sets a setting value. @@ -76,7 +77,9 @@ impl Commands { ConversationCommands::Sync => db.sync_with_client().await, }, Commands::Messages { command: cmd } => match cmd { - MessageCommands::List { conversation_id } => db.print_messages(&conversation_id).await, + MessageCommands::List { conversation_id } => { + db.print_messages(&conversation_id).await + } }, Commands::Settings { command: cmd } => match cmd { SettingsCommands::Get { key } => db.get_setting(key).await, @@ -88,15 +91,17 @@ impl Commands { } struct DbClient { - database: Database + database: Database, } impl DbClient { fn database_path() -> PathBuf { - env::var("KORDOPHONE_DB_PATH").unwrap_or_else(|_| { - let temp_dir = env::temp_dir(); - temp_dir.join("kpcli_chat.db").to_str().unwrap().to_string() - }).into() + env::var("KORDOPHONE_DB_PATH") + .unwrap_or_else(|_| { + let temp_dir = env::temp_dir(); + temp_dir.join("kpcli_chat.db").to_str().unwrap().to_string() + }) + .into() } pub fn new() -> Result { @@ -106,13 +111,14 @@ impl DbClient { println!("kpcli: Using db at {}", path_str); let db = Database::new(path_str)?; - Ok( Self { database: db }) + Ok(Self { database: db }) } pub async fn print_conversations(&mut self) -> Result<()> { - let all_conversations = self.database.with_repository(|repository| { - repository.all_conversations(i32::MAX, 0) - }).await?; + let all_conversations = self + .database + .with_repository(|repository| repository.all_conversations(i32::MAX, 0)) + .await?; println!("{} Conversations: ", all_conversations.len()); for conversation in all_conversations { @@ -123,9 +129,10 @@ impl DbClient { } pub async fn print_messages(&mut self, conversation_id: &str) -> Result<()> { - let messages = self.database.with_repository(|repository| { - repository.get_messages_for_conversation(conversation_id) - }).await?; + let messages = self + .database + .with_repository(|repository| repository.get_messages_for_conversation(conversation_id)) + .await?; for message in messages { println!("{}", MessagePrinter::new(&message.into())); @@ -136,85 +143,97 @@ impl DbClient { pub async fn sync_with_client(&mut self) -> Result<()> { let mut client = client::make_api_client_from_env(); let fetched_conversations = client.get_conversations().await?; - let db_conversations: Vec = fetched_conversations.into_iter() + let db_conversations: Vec = fetched_conversations + .into_iter() .map(kordophone_db::models::Conversation::from) .collect(); // Process each conversation for conversation in db_conversations { let conversation_id = conversation.guid.clone(); - + // Insert the conversation - self.database.with_repository(|repository| { - repository.insert_conversation(conversation) - }).await?; + self.database + .with_repository(|repository| repository.insert_conversation(conversation)) + .await?; // Fetch and sync messages for this conversation - let messages = client.get_messages(&conversation_id, None, None, None).await?; - let db_messages: Vec = messages.into_iter() + let messages = client + .get_messages(&conversation_id, None, None, None) + .await?; + let db_messages: Vec = messages + .into_iter() .map(kordophone_db::models::Message::from) .collect(); // Insert each message - self.database.with_repository(|repository| -> Result<()> { - for message in db_messages { - repository.insert_message(&conversation_id, message)?; - } + self.database + .with_repository(|repository| -> Result<()> { + for message in db_messages { + repository.insert_message(&conversation_id, message)?; + } - Ok(()) - }).await?; + Ok(()) + }) + .await?; } Ok(()) } pub async fn get_setting(&mut self, key: Option) -> Result<()> { - self.database.with_settings(|settings| { - match key { - Some(key) => { - // Get a specific setting - let value: Option = settings.get(&key)?; - match value { - Some(v) => println!("{} = {}", key, v), - None => println!("Setting '{}' not found", key), + self.database + .with_settings(|settings| { + match key { + Some(key) => { + // Get a specific setting + let value: Option = settings.get(&key)?; + match value { + Some(v) => println!("{} = {}", key, v), + None => println!("Setting '{}' not found", key), + } } - }, - None => { - // List all settings - let keys = settings.list_keys()?; - if keys.is_empty() { - println!("No settings found"); - } else { - println!("Settings:"); - for key in keys { - let value: Option = settings.get(&key)?; - match value { - Some(v) => println!(" {} = {}", key, v), - None => println!(" {} = ", key), + None => { + // List all settings + let keys = settings.list_keys()?; + if keys.is_empty() { + println!("No settings found"); + } else { + println!("Settings:"); + for key in keys { + let value: Option = settings.get(&key)?; + match value { + Some(v) => println!(" {} = {}", key, v), + None => println!(" {} = ", key), + } } } } } - } - - Ok(()) - }).await + + Ok(()) + }) + .await } pub async fn put_setting(&mut self, key: String, value: String) -> Result<()> { - self.database.with_settings(|settings| { - settings.put(&key, &value)?; - Ok(()) - }).await + self.database + .with_settings(|settings| { + settings.put(&key, &value)?; + Ok(()) + }) + .await } pub async fn delete_setting(&mut self, key: String) -> Result<()> { - self.database.with_settings(|settings| { - let count = settings.del(&key)?; - if count == 0 { - println!("Setting '{}' not found", key); - } - Ok(()) - }).await + self.database + .with_settings(|settings| { + let count = settings.del(&key)?; + if count == 0 { + println!("Setting '{}' not found", key); + } + Ok(()) + }) + .await } } diff --git a/kpcli/src/main.rs b/kpcli/src/main.rs index 2cf4ba8..a485c1a 100644 --- a/kpcli/src/main.rs +++ b/kpcli/src/main.rs @@ -1,7 +1,7 @@ mod client; +mod daemon; mod db; mod printers; -mod daemon; use anyhow::Result; use clap::{Parser, Subcommand}; @@ -33,7 +33,7 @@ enum Commands { Daemon { #[command(subcommand)] command: daemon::Commands, - } + }, } async fn run_command(command: Commands) -> Result<()> { @@ -50,7 +50,7 @@ fn initialize_logging() { .map(|s| s.parse::().unwrap_or(LevelFilter::Info)) .unwrap_or(LevelFilter::Info); - env_logger::Builder::from_default_env() + env_logger::Builder::from_default_env() .format_timestamp_secs() .filter_level(log_level) .init(); @@ -62,7 +62,8 @@ async fn main() { let cli = Cli::parse(); - run_command(cli.command).await + run_command(cli.command) + .await .map_err(|e| println!("Error: {}", e)) .err(); } diff --git a/kpcli/src/printers.rs b/kpcli/src/printers.rs index f9e0c0a..6289419 100644 --- a/kpcli/src/printers.rs +++ b/kpcli/src/printers.rs @@ -1,9 +1,9 @@ -use std::fmt::Display; -use std::collections::HashMap; -use time::OffsetDateTime; -use pretty::RcDoc; use dbus::arg::{self, RefArg}; use kordophone::model::message::AttachmentMetadata; +use pretty::RcDoc; +use std::collections::HashMap; +use std::fmt::Display; +use time::OffsetDateTime; pub struct PrintableConversation { pub guid: String, @@ -17,7 +17,7 @@ pub struct PrintableConversation { impl From for PrintableConversation { fn from(value: kordophone::model::Conversation) -> Self { Self { - guid: value.guid, + guid: value.guid, date: value.date, unread_count: value.unread_count, last_message_preview: value.last_message_preview, @@ -34,7 +34,11 @@ impl From for PrintableConversation { date: OffsetDateTime::from_unix_timestamp(value.date.and_utc().timestamp()).unwrap(), unread_count: value.unread_count.into(), last_message_preview: value.last_message_preview, - participants: value.participants.into_iter().map(|p| p.display_name()).collect(), + participants: value + .participants + .into_iter() + .map(|p| p.display_name()) + .collect(), display_name: value.display_name, } } @@ -44,17 +48,33 @@ impl From for PrintableConversation { fn from(value: arg::PropMap) -> Self { Self { guid: value.get("guid").unwrap().as_str().unwrap().to_string(), - date: OffsetDateTime::from_unix_timestamp(value.get("date").unwrap().as_i64().unwrap()).unwrap(), - unread_count: value.get("unread_count").unwrap().as_i64().unwrap().try_into().unwrap(), - last_message_preview: value.get("last_message_preview").unwrap().as_str().map(|s| s.to_string()), - participants: value.get("participants") + date: OffsetDateTime::from_unix_timestamp(value.get("date").unwrap().as_i64().unwrap()) + .unwrap(), + unread_count: value + .get("unread_count") + .unwrap() + .as_i64() + .unwrap() + .try_into() + .unwrap(), + last_message_preview: value + .get("last_message_preview") + .unwrap() + .as_str() + .map(|s| s.to_string()), + participants: value + .get("participants") .unwrap() .0 .as_iter() .unwrap() .map(|s| s.as_str().unwrap().to_string()) .collect(), - display_name: value.get("display_name").unwrap().as_str().map(|s| s.to_string()), + display_name: value + .get("display_name") + .unwrap() + .as_str() + .map(|s| s.to_string()), } } } @@ -97,19 +117,22 @@ impl From for PrintableMessage { impl From for PrintableMessage { fn from(value: arg::PropMap) -> Self { // Parse file transfer GUIDs from JSON if present - let file_transfer_guids = value.get("file_transfer_guids") + let file_transfer_guids = value + .get("file_transfer_guids") .and_then(|v| v.as_str()) .and_then(|json_str| serde_json::from_str(json_str).ok()) .unwrap_or_default(); // Parse attachment metadata from JSON if present - let attachment_metadata = value.get("attachment_metadata") + let attachment_metadata = value + .get("attachment_metadata") .and_then(|v| v.as_str()) .and_then(|json_str| serde_json::from_str(json_str).ok()); Self { guid: value.get("id").unwrap().as_str().unwrap().to_string(), - date: OffsetDateTime::from_unix_timestamp(value.get("date").unwrap().as_i64().unwrap()).unwrap(), + date: OffsetDateTime::from_unix_timestamp(value.get("date").unwrap().as_i64().unwrap()) + .unwrap(), sender: value.get("sender").unwrap().as_str().unwrap().to_string(), text: value.get("text").unwrap().as_str().unwrap().to_string(), file_transfer_guids, @@ -119,12 +142,13 @@ impl From for PrintableMessage { } pub struct ConversationPrinter<'a> { - doc: RcDoc<'a, PrintableConversation> + doc: RcDoc<'a, PrintableConversation>, } impl<'a> ConversationPrinter<'a> { pub fn new(conversation: &'a PrintableConversation) -> Self { - let preview = conversation.last_message_preview + let preview = conversation + .last_message_preview .as_deref() .unwrap_or("") .replace('\n', " "); @@ -134,33 +158,31 @@ impl<'a> ConversationPrinter<'a> { RcDoc::line() .append("Display Name: ") .append(conversation.display_name.as_deref().unwrap_or("")) - .append(RcDoc::line()) + .append(RcDoc::line()) .append("Date: ") .append(conversation.date.to_string()) - .append(RcDoc::line()) + .append(RcDoc::line()) .append("Unread Count: ") .append(conversation.unread_count.to_string()) - .append(RcDoc::line()) + .append(RcDoc::line()) .append("Participants: ") .append("[") - .append(RcDoc::line() - .append( - conversation.participants - .iter() - .map(|name| - RcDoc::text(name) - .append(",") - .append(RcDoc::line()) - ) - .fold(RcDoc::nil(), |acc, x| acc.append(x)) - ) - .nest(4) + .append( + RcDoc::line() + .append( + conversation + .participants + .iter() + .map(|name| RcDoc::text(name).append(",").append(RcDoc::line())) + .fold(RcDoc::nil(), |acc, x| acc.append(x)), + ) + .nest(4), ) .append("]") - .append(RcDoc::line()) + .append(RcDoc::line()) .append("Last Message Preview: ") .append(preview) - .nest(4) + .nest(4), ) .append(RcDoc::line()) .append(">"); @@ -176,7 +198,7 @@ impl Display for ConversationPrinter<'_> { } pub struct MessagePrinter<'a> { - doc: RcDoc<'a, PrintableMessage> + doc: RcDoc<'a, PrintableMessage>, } impl Display for MessagePrinter<'_> { @@ -187,37 +209,40 @@ impl Display for MessagePrinter<'_> { impl<'a> MessagePrinter<'a> { pub fn new(message: &'a PrintableMessage) -> Self { - let mut doc = RcDoc::text(format!(" MessagePrinter<'a> { attachment_doc }) - .fold(RcDoc::nil(), |acc, x| acc.append(x)) - ) - .nest(4) - ); + .fold(RcDoc::nil(), |acc, x| acc.append(x)), + ) + .nest(4), + ); } doc = doc.append(RcDoc::line()).append(">"); MessagePrinter { doc } } -} \ No newline at end of file +}