implements settings, conversation dbus encoding
This commit is contained in:
68
Cargo.lock
generated
68
Cargo.lock
generated
@@ -509,14 +509,14 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "env_logger"
|
name = "env_logger"
|
||||||
version = "0.11.6"
|
version = "0.11.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0"
|
checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstream",
|
"anstream",
|
||||||
"anstyle",
|
"anstyle",
|
||||||
"env_filter",
|
"env_filter",
|
||||||
"humantime",
|
"jiff",
|
||||||
"log",
|
"log",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -712,12 +712,6 @@ version = "1.0.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "humantime"
|
|
||||||
version = "2.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper"
|
name = "hyper"
|
||||||
version = "0.14.28"
|
version = "0.14.28"
|
||||||
@@ -806,6 +800,30 @@ version = "1.0.11"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jiff"
|
||||||
|
version = "0.2.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5a064218214dc6a10fbae5ec5fa888d80c45d611aba169222fc272072bf7aef6"
|
||||||
|
dependencies = [
|
||||||
|
"jiff-static",
|
||||||
|
"log",
|
||||||
|
"portable-atomic",
|
||||||
|
"portable-atomic-util",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jiff-static"
|
||||||
|
version = "0.2.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "199b7932d97e325aff3a7030e141eafe7f2c6268e1d1b24859b753a627f45254"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.72"
|
version = "0.3.72"
|
||||||
@@ -846,6 +864,7 @@ dependencies = [
|
|||||||
"diesel",
|
"diesel",
|
||||||
"diesel_migrations",
|
"diesel_migrations",
|
||||||
"kordophone",
|
"kordophone",
|
||||||
|
"log",
|
||||||
"serde",
|
"serde",
|
||||||
"time",
|
"time",
|
||||||
"tokio",
|
"tokio",
|
||||||
@@ -950,9 +969,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.25"
|
version = "0.4.27"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f"
|
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
@@ -1141,6 +1160,21 @@ version = "0.3.30"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
|
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "portable-atomic"
|
||||||
|
version = "1.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "portable-atomic-util"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
|
||||||
|
dependencies = [
|
||||||
|
"portable-atomic",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "powerfmt"
|
name = "powerfmt"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@@ -1167,18 +1201,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.92"
|
version = "1.0.95"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
|
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.36"
|
version = "1.0.40"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
|
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
@@ -1422,9 +1456,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.90"
|
version = "2.0.101"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31"
|
checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ chrono = "0.4.38"
|
|||||||
diesel = { version = "2.2.6", features = ["chrono", "sqlite", "time"] }
|
diesel = { version = "2.2.6", features = ["chrono", "sqlite", "time"] }
|
||||||
diesel_migrations = { version = "2.2.0", features = ["sqlite"] }
|
diesel_migrations = { version = "2.2.0", features = ["sqlite"] }
|
||||||
kordophone = { path = "../kordophone" }
|
kordophone = { path = "../kordophone" }
|
||||||
|
log = "0.4.27"
|
||||||
serde = { version = "1.0.215", features = ["derive"] }
|
serde = { version = "1.0.215", features = ["derive"] }
|
||||||
time = "0.3.37"
|
time = "0.3.37"
|
||||||
tokio = "1.44.2"
|
tokio = "1.44.2"
|
||||||
|
|||||||
@@ -130,6 +130,53 @@ impl<'a> Repository<'a> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn insert_messages(&mut self, conversation_guid: &str, in_messages: Vec<Message>) -> Result<()> {
|
||||||
|
use crate::schema::messages::dsl::*;
|
||||||
|
use crate::schema::conversation_messages::dsl::*;
|
||||||
|
|
||||||
|
// Local insertable struct for the join table
|
||||||
|
#[derive(Insertable)]
|
||||||
|
#[diesel(table_name = crate::schema::conversation_messages)]
|
||||||
|
struct InsertableConversationMessage {
|
||||||
|
pub conversation_id: String,
|
||||||
|
pub message_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
if in_messages.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the collections of insertable records
|
||||||
|
let mut db_messages: Vec<MessageRecord> = Vec::with_capacity(in_messages.len());
|
||||||
|
let mut conv_msg_records: Vec<InsertableConversationMessage> = Vec::with_capacity(in_messages.len());
|
||||||
|
|
||||||
|
for message in in_messages {
|
||||||
|
// Handle participant if message has a remote sender
|
||||||
|
let sender = message.sender.clone();
|
||||||
|
let mut db_message: MessageRecord = message.into();
|
||||||
|
db_message.sender_participant_id = self.get_or_create_participant(&sender);
|
||||||
|
|
||||||
|
conv_msg_records.push(InsertableConversationMessage {
|
||||||
|
conversation_id: conversation_guid.to_string(),
|
||||||
|
message_id: db_message.id.clone(),
|
||||||
|
});
|
||||||
|
|
||||||
|
db_messages.push(db_message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Batch insert or replace messages
|
||||||
|
diesel::replace_into(messages)
|
||||||
|
.values(&db_messages)
|
||||||
|
.execute(self.connection)?;
|
||||||
|
|
||||||
|
// Batch insert the conversation-message links
|
||||||
|
diesel::replace_into(conversation_messages)
|
||||||
|
.values(&conv_msg_records)
|
||||||
|
.execute(self.connection)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_messages_for_conversation(&mut self, conversation_guid: &str) -> Result<Vec<Message>> {
|
pub fn get_messages_for_conversation(&mut self, conversation_guid: &str) -> Result<Vec<Message>> {
|
||||||
use crate::schema::messages::dsl::*;
|
use crate::schema::messages::dsl::*;
|
||||||
use crate::schema::conversation_messages::dsl::*;
|
use crate::schema::conversation_messages::dsl::*;
|
||||||
|
|||||||
@@ -26,13 +26,13 @@ fn participants_vec_equal_ignoring_id(a: &[Participant], b: &[Participant]) -> b
|
|||||||
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))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[tokio::test]
|
||||||
fn test_database_init() {
|
async fn test_database_init() {
|
||||||
let _ = Database::new_in_memory().unwrap();
|
let _ = Database::new_in_memory().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[tokio::test]
|
||||||
fn test_add_conversation() {
|
async fn test_add_conversation() {
|
||||||
let mut db = Database::new_in_memory().unwrap();
|
let mut db = Database::new_in_memory().unwrap();
|
||||||
db.with_repository(|repository| {
|
db.with_repository(|repository| {
|
||||||
let guid = "test";
|
let guid = "test";
|
||||||
@@ -62,11 +62,11 @@ fn test_add_conversation() {
|
|||||||
// 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();
|
let conversation = repository.get_conversation_by_guid(guid).unwrap().unwrap();
|
||||||
assert_eq!(conversation.display_name.unwrap(), "Modified Conversation");
|
assert_eq!(conversation.display_name.unwrap(), "Modified Conversation");
|
||||||
});
|
}).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[tokio::test]
|
||||||
fn test_conversation_participants() {
|
async fn test_conversation_participants() {
|
||||||
let mut db = Database::new_in_memory().unwrap();
|
let mut db = Database::new_in_memory().unwrap();
|
||||||
db.with_repository(|repository| {
|
db.with_repository(|repository| {
|
||||||
let participants: Vec<Participant> = vec!["one".into(), "two".into()];
|
let participants: Vec<Participant> = vec!["one".into(), "two".into()];
|
||||||
@@ -97,11 +97,11 @@ fn test_conversation_participants() {
|
|||||||
let read_participants: Vec<Participant> = read_conversation.participants;
|
let read_participants: Vec<Participant> = read_conversation.participants;
|
||||||
|
|
||||||
assert!(participants_vec_equal_ignoring_id(&participants, &read_participants));
|
assert!(participants_vec_equal_ignoring_id(&participants, &read_participants));
|
||||||
});
|
}).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[tokio::test]
|
||||||
fn test_all_conversations_with_participants() {
|
async fn test_all_conversations_with_participants() {
|
||||||
let mut db = Database::new_in_memory().unwrap();
|
let mut db = Database::new_in_memory().unwrap();
|
||||||
db.with_repository(|repository| {
|
db.with_repository(|repository| {
|
||||||
// Create two conversations with different participants
|
// Create two conversations with different participants
|
||||||
@@ -136,11 +136,11 @@ fn test_all_conversations_with_participants() {
|
|||||||
|
|
||||||
assert!(participants_vec_equal_ignoring_id(&conv1.participants, &participants1));
|
assert!(participants_vec_equal_ignoring_id(&conv1.participants, &participants1));
|
||||||
assert!(participants_vec_equal_ignoring_id(&conv2.participants, &participants2));
|
assert!(participants_vec_equal_ignoring_id(&conv2.participants, &participants2));
|
||||||
});
|
}).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[tokio::test]
|
||||||
fn test_messages() {
|
async fn test_messages() {
|
||||||
let mut db = Database::new_in_memory().unwrap();
|
let mut db = Database::new_in_memory().unwrap();
|
||||||
db.with_repository(|repository| {
|
db.with_repository(|repository| {
|
||||||
// First create a conversation with participants
|
// First create a conversation with participants
|
||||||
@@ -185,11 +185,11 @@ fn test_messages() {
|
|||||||
} else {
|
} else {
|
||||||
panic!("Expected Remote participant. Got: {:?}", retrieved_message2.sender);
|
panic!("Expected Remote participant. Got: {:?}", retrieved_message2.sender);
|
||||||
}
|
}
|
||||||
});
|
}).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[tokio::test]
|
||||||
fn test_message_ordering() {
|
async fn test_message_ordering() {
|
||||||
let mut db = Database::new_in_memory().unwrap();
|
let mut db = Database::new_in_memory().unwrap();
|
||||||
db.with_repository(|repository| {
|
db.with_repository(|repository| {
|
||||||
// Create a conversation
|
// Create a conversation
|
||||||
@@ -229,11 +229,93 @@ fn test_message_ordering() {
|
|||||||
for i in 1..messages.len() {
|
for i in 1..messages.len() {
|
||||||
assert!(messages[i].date > messages[i-1].date);
|
assert!(messages[i].date > messages[i-1].date);
|
||||||
}
|
}
|
||||||
});
|
}).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[tokio::test]
|
||||||
fn test_settings() {
|
async fn test_insert_messages_batch() {
|
||||||
|
let mut db = Database::new_in_memory().unwrap();
|
||||||
|
db.with_repository(|repository| {
|
||||||
|
// Create a conversation with two remote participants
|
||||||
|
let participants: Vec<Participant> = vec!["Alice".into(), "Bob".into()];
|
||||||
|
let conversation = ConversationBuilder::new()
|
||||||
|
.display_name("Batch Chat")
|
||||||
|
.participants(participants.clone())
|
||||||
|
.build();
|
||||||
|
let conversation_id = conversation.guid.clone();
|
||||||
|
repository.insert_conversation(conversation).unwrap();
|
||||||
|
|
||||||
|
// 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 message2 = Message::builder()
|
||||||
|
.text("Hello".to_string())
|
||||||
|
.sender("Alice".into())
|
||||||
|
.date(now + chrono::Duration::seconds(1))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let message3 = Message::builder()
|
||||||
|
.text("How are you?".to_string())
|
||||||
|
.sender("Bob".into())
|
||||||
|
.date(now + chrono::Duration::seconds(2))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let message4 = Message::builder()
|
||||||
|
.text("Great!".to_string())
|
||||||
|
.date(now + chrono::Duration::seconds(3))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let original_messages = vec![
|
||||||
|
message1.clone(),
|
||||||
|
message2.clone(),
|
||||||
|
message3.clone(),
|
||||||
|
message4.clone(),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Batch insert the messages
|
||||||
|
repository
|
||||||
|
.insert_messages(&conversation_id, original_messages.clone())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Retrieve messages and verify
|
||||||
|
let retrieved_messages = repository.get_messages_for_conversation(&conversation_id).unwrap();
|
||||||
|
assert_eq!(retrieved_messages.len(), original_messages.len());
|
||||||
|
|
||||||
|
// Ensure ordering by date
|
||||||
|
for i in 1..retrieved_messages.len() {
|
||||||
|
assert!(retrieved_messages[i].date > retrieved_messages[i - 1].date);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that all messages are present with correct content and sender
|
||||||
|
for original in &original_messages {
|
||||||
|
let retrieved = retrieved_messages
|
||||||
|
.iter()
|
||||||
|
.find(|m| m.id == original.id)
|
||||||
|
.expect("Message not found");
|
||||||
|
assert_eq!(retrieved.text, original.text);
|
||||||
|
|
||||||
|
match (&original.sender, &retrieved.sender) {
|
||||||
|
(Participant::Me, Participant::Me) => {}
|
||||||
|
(
|
||||||
|
Participant::Remote { display_name: o_name, .. },
|
||||||
|
Participant::Remote { display_name: r_name, .. },
|
||||||
|
) => assert_eq!(o_name, r_name),
|
||||||
|
_ => panic!(
|
||||||
|
"Sender mismatch: original {:?}, retrieved {:?}",
|
||||||
|
original.sender, retrieved.sender
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_settings() {
|
||||||
let mut db = Database::new_in_memory().unwrap();
|
let mut db = Database::new_in_memory().unwrap();
|
||||||
db.with_settings(|settings| {
|
db.with_settings(|settings| {
|
||||||
settings.put("test", &"test".to_string()).unwrap();
|
settings.put("test", &"test".to_string()).unwrap();
|
||||||
@@ -244,5 +326,27 @@ fn test_settings() {
|
|||||||
|
|
||||||
let keys = settings.list_keys().unwrap();
|
let keys = settings.list_keys().unwrap();
|
||||||
assert_eq!(keys.len(), 0);
|
assert_eq!(keys.len(), 0);
|
||||||
});
|
|
||||||
|
// Try encoding a struct
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Eq)]
|
||||||
|
struct TestStruct {
|
||||||
|
name: String,
|
||||||
|
age: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
let test_struct = TestStruct {
|
||||||
|
name: "James".to_string(),
|
||||||
|
age: 35,
|
||||||
|
};
|
||||||
|
|
||||||
|
settings.put("test_struct", &test_struct).unwrap();
|
||||||
|
assert_eq!(settings.get::<TestStruct>("test_struct").unwrap().unwrap(), test_struct);
|
||||||
|
|
||||||
|
// Test with an option<string>
|
||||||
|
settings.put("test_struct_option", &Option::<String>::None).unwrap();
|
||||||
|
assert!(settings.get::<Option<String>>("test_struct_option").unwrap().unwrap().is_none());
|
||||||
|
|
||||||
|
settings.put("test_struct_option", &Option::<String>::Some("test".to_string())).unwrap();
|
||||||
|
assert_eq!(settings.get::<Option<String>>("test_struct_option").unwrap().unwrap(), Some("test".to_string()));
|
||||||
|
}).await;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,10 @@
|
|||||||
'id' (string): Unique identifier
|
'id' (string): Unique identifier
|
||||||
'title' (string): Display name
|
'title' (string): Display name
|
||||||
'last_message' (string): Preview text
|
'last_message' (string): Preview text
|
||||||
'is_unread' (boolean): Unread status"/>
|
'is_unread' (boolean): Unread status
|
||||||
|
'date' (int64): Date of last message
|
||||||
|
'participants' (array of strings): List of participants
|
||||||
|
'unread_count' (int32): Number of unread messages"/>
|
||||||
</arg>
|
</arg>
|
||||||
</method>
|
</method>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use tokio::sync::oneshot;
|
use tokio::sync::oneshot;
|
||||||
use kordophone_db::models::Conversation;
|
use kordophone_db::models::Conversation;
|
||||||
|
use crate::daemon::settings::Settings;
|
||||||
|
|
||||||
pub type Reply<T> = oneshot::Sender<T>;
|
pub type Reply<T> = oneshot::Sender<T>;
|
||||||
|
|
||||||
@@ -13,6 +14,12 @@ pub enum Event {
|
|||||||
|
|
||||||
/// Returns all known conversations from the database.
|
/// Returns all known conversations from the database.
|
||||||
GetAllConversations(Reply<Vec<Conversation>>),
|
GetAllConversations(Reply<Vec<Conversation>>),
|
||||||
|
|
||||||
|
/// Returns all known settings from the database.
|
||||||
|
GetAllSettings(Reply<Settings>),
|
||||||
|
|
||||||
|
/// Update settings in the database.
|
||||||
|
UpdateSettings(Settings, Reply<()>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -50,6 +50,10 @@ impl TokenStore for DatabaseTokenStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod target {
|
||||||
|
pub static SYNC: &str = "sync";
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Daemon {
|
pub struct Daemon {
|
||||||
pub event_sender: Sender<Event>,
|
pub event_sender: Sender<Event>,
|
||||||
event_receiver: Receiver<Event>,
|
event_receiver: Receiver<Event>,
|
||||||
@@ -110,6 +114,25 @@ impl Daemon {
|
|||||||
let conversations = self.get_conversations().await;
|
let conversations = self.get_conversations().await;
|
||||||
reply.send(conversations).unwrap();
|
reply.send(conversations).unwrap();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Event::GetAllSettings(reply) => {
|
||||||
|
let settings = self.get_settings().await
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
log::error!("Failed to get settings: {:#?}", e);
|
||||||
|
Settings::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
reply.send(settings).unwrap();
|
||||||
|
},
|
||||||
|
|
||||||
|
Event::UpdateSettings(settings, reply) => {
|
||||||
|
self.update_settings(settings).await
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
log::error!("Failed to update settings: {}", e);
|
||||||
|
});
|
||||||
|
|
||||||
|
reply.send(()).unwrap();
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,32 +141,9 @@ impl Daemon {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn sync_all_conversations_impl(mut database: Arc<Mutex<Database>>) -> Result<()> {
|
async fn sync_all_conversations_impl(mut database: Arc<Mutex<Database>>) -> Result<()> {
|
||||||
log::info!("Starting conversation sync");
|
log::info!(target: target::SYNC, "Starting conversation sync");
|
||||||
|
|
||||||
// Get client from the database
|
let mut client = Self::get_client_impl(database.clone()).await?;
|
||||||
let settings = database.with_settings(|s| Settings::from_db(s))
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let server_url = settings.server_url
|
|
||||||
.ok_or(DaemonError::ClientNotConfigured)?;
|
|
||||||
|
|
||||||
let mut client = HTTPAPIClient::new(
|
|
||||||
server_url.parse().unwrap(),
|
|
||||||
match (settings.username, settings.credential_item) {
|
|
||||||
(Some(username), Some(password)) => Some(
|
|
||||||
Credentials {
|
|
||||||
username,
|
|
||||||
password,
|
|
||||||
}
|
|
||||||
),
|
|
||||||
_ => None,
|
|
||||||
},
|
|
||||||
DatabaseTokenStore { database: database.clone() }
|
|
||||||
);
|
|
||||||
|
|
||||||
// This function needed to implement TokenManagement
|
|
||||||
// let token = database.lock().await.get_token();
|
|
||||||
// TODO: Clent.token = token
|
|
||||||
|
|
||||||
// Fetch conversations from server
|
// Fetch conversations from server
|
||||||
let fetched_conversations = client.get_conversations().await?;
|
let fetched_conversations = client.get_conversations().await?;
|
||||||
@@ -152,6 +152,7 @@ impl Daemon {
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Process each conversation
|
// Process each conversation
|
||||||
|
let num_conversations = db_conversations.len();
|
||||||
for conversation in db_conversations {
|
for conversation in db_conversations {
|
||||||
let conversation_id = conversation.guid.clone();
|
let conversation_id = conversation.guid.clone();
|
||||||
|
|
||||||
@@ -159,21 +160,18 @@ impl Daemon {
|
|||||||
database.with_repository(|r| r.insert_conversation(conversation)).await?;
|
database.with_repository(|r| r.insert_conversation(conversation)).await?;
|
||||||
|
|
||||||
// Fetch and sync messages for this conversation
|
// Fetch and sync messages for this conversation
|
||||||
|
log::info!(target: target::SYNC, "Fetching messages for conversation {}", conversation_id);
|
||||||
let messages = client.get_messages(&conversation_id).await?;
|
let messages = client.get_messages(&conversation_id).await?;
|
||||||
let db_messages: Vec<kordophone_db::models::Message> = messages.into_iter()
|
let db_messages: Vec<kordophone_db::models::Message> = messages.into_iter()
|
||||||
.map(|m| kordophone_db::models::Message::from(m))
|
.map(|m| kordophone_db::models::Message::from(m))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Insert each message
|
// Insert each message
|
||||||
database.with_repository(|r| -> Result<()> {
|
log::info!(target: target::SYNC, "Inserting {} messages for conversation {}", db_messages.len(), conversation_id);
|
||||||
for message in db_messages {
|
database.with_repository(|r| r.insert_messages(&conversation_id, db_messages)).await?;
|
||||||
r.insert_message(&conversation_id, message)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}).await?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log::info!(target: target::SYNC, "Synchronized {} conversations", num_conversations);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,8 +183,16 @@ impl Daemon {
|
|||||||
Ok(settings)
|
Ok(settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn update_settings(&mut self, settings: Settings) -> Result<()> {
|
||||||
|
self.database.with_settings(|s| settings.save(s)).await
|
||||||
|
}
|
||||||
|
|
||||||
async fn get_client(&mut self) -> Result<HTTPAPIClient<DatabaseTokenStore>> {
|
async fn get_client(&mut self) -> Result<HTTPAPIClient<DatabaseTokenStore>> {
|
||||||
let settings = self.database.with_settings(|s|
|
Self::get_client_impl(self.database.clone()).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_client_impl(mut database: Arc<Mutex<Database>>) -> Result<HTTPAPIClient<DatabaseTokenStore>> {
|
||||||
|
let settings = database.with_settings(|s|
|
||||||
Settings::from_db(s)
|
Settings::from_db(s)
|
||||||
).await?;
|
).await?;
|
||||||
|
|
||||||
@@ -205,7 +211,7 @@ impl Daemon {
|
|||||||
),
|
),
|
||||||
_ => None,
|
_ => None,
|
||||||
},
|
},
|
||||||
DatabaseTokenStore { database: self.database.clone() }
|
DatabaseTokenStore { database: database.clone() }
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(client)
|
Ok(client)
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ pub struct Settings {
|
|||||||
|
|
||||||
impl Settings {
|
impl Settings {
|
||||||
pub fn from_db(db_settings: &mut DbSettings) -> Result<Self> {
|
pub fn from_db(db_settings: &mut DbSettings) -> Result<Self> {
|
||||||
let server_url = db_settings.get(keys::SERVER_URL)?;
|
let server_url: Option<String> = db_settings.get(keys::SERVER_URL)?;
|
||||||
let username = db_settings.get(keys::USERNAME)?;
|
let username: Option<String> = db_settings.get(keys::USERNAME)?;
|
||||||
let credential_item = db_settings.get(keys::CREDENTIAL_ITEM)?;
|
let credential_item: Option<String> = db_settings.get(keys::CREDENTIAL_ITEM)?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
server_url,
|
server_url,
|
||||||
@@ -28,9 +28,25 @@ impl Settings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn save(&self, db_settings: &mut DbSettings) -> Result<()> {
|
pub fn save(&self, db_settings: &mut DbSettings) -> Result<()> {
|
||||||
db_settings.put(keys::SERVER_URL, &self.server_url)?;
|
if let Some(server_url) = &self.server_url {
|
||||||
db_settings.put(keys::USERNAME, &self.username)?;
|
db_settings.put(keys::SERVER_URL, &server_url)?;
|
||||||
db_settings.put(keys::CREDENTIAL_ITEM, &self.credential_item)?;
|
}
|
||||||
|
if let Some(username) = &self.username {
|
||||||
|
db_settings.put(keys::USERNAME, &username)?;
|
||||||
|
}
|
||||||
|
if let Some(credential_item) = &self.credential_item {
|
||||||
|
db_settings.put(keys::CREDENTIAL_ITEM, &credential_item)?;
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Settings {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
server_url: None,
|
||||||
|
username: None,
|
||||||
|
credential_item: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +1,14 @@
|
|||||||
use dbus::arg;
|
use dbus::arg;
|
||||||
use dbus_tree::MethodErr;
|
use dbus_tree::MethodErr;
|
||||||
use std::sync::Arc;
|
use tokio::sync::mpsc;
|
||||||
use tokio::sync::{Mutex, MutexGuard};
|
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use tokio::sync::oneshot;
|
use tokio::sync::oneshot;
|
||||||
use tokio::sync::mpsc;
|
|
||||||
use futures_util::future::FutureExt;
|
|
||||||
|
|
||||||
use crate::daemon::{
|
use crate::daemon::{
|
||||||
Daemon,
|
|
||||||
DaemonResult,
|
DaemonResult,
|
||||||
events::{Event, Reply},
|
events::{Event, Reply},
|
||||||
|
settings::Settings,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::dbus::interface::NetBuzzertKordophoneRepository as DbusRepository;
|
use crate::dbus::interface::NetBuzzertKordophoneRepository as DbusRepository;
|
||||||
@@ -63,7 +60,10 @@ impl DbusRepository for ServerImpl {
|
|||||||
map.insert("guid".into(), arg::Variant(Box::new(conv.guid)));
|
map.insert("guid".into(), arg::Variant(Box::new(conv.guid)));
|
||||||
map.insert("display_name".into(), arg::Variant(Box::new(conv.display_name.unwrap_or_default())));
|
map.insert("display_name".into(), arg::Variant(Box::new(conv.display_name.unwrap_or_default())));
|
||||||
map.insert("unread_count".into(), arg::Variant(Box::new(conv.unread_count as i32)));
|
map.insert("unread_count".into(), arg::Variant(Box::new(conv.unread_count as i32)));
|
||||||
map
|
map.insert("last_message_preview".into(), arg::Variant(Box::new(conv.last_message_preview.unwrap_or_default())));
|
||||||
|
map.insert("participants".into(), arg::Variant(Box::new(conv.participants.into_iter().map(|p| p.display_name()).collect::<Vec<String>>())));
|
||||||
|
map.insert("date".into(), arg::Variant(Box::new(conv.date.and_utc().timestamp())));
|
||||||
|
map
|
||||||
}).collect();
|
}).collect();
|
||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
@@ -77,35 +77,77 @@ impl DbusRepository for ServerImpl {
|
|||||||
|
|
||||||
impl DbusSettings for ServerImpl {
|
impl DbusSettings for ServerImpl {
|
||||||
fn set_server(&mut self, url: String, user: String) -> Result<(), dbus::MethodErr> {
|
fn set_server(&mut self, url: String, user: String) -> Result<(), dbus::MethodErr> {
|
||||||
todo!()
|
self.send_event_sync(|r|
|
||||||
|
Event::UpdateSettings(Settings {
|
||||||
|
server_url: Some(url),
|
||||||
|
username: Some(user),
|
||||||
|
credential_item: None,
|
||||||
|
}, r)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_credential_item_(&mut self, item_path: dbus::Path<'static>) -> Result<(), dbus::MethodErr> {
|
fn set_credential_item_(&mut self, item_path: dbus::Path<'static>) -> Result<(), dbus::MethodErr> {
|
||||||
todo!()
|
self.send_event_sync(|r|
|
||||||
|
Event::UpdateSettings(Settings {
|
||||||
|
server_url: None,
|
||||||
|
username: None,
|
||||||
|
credential_item: Some(item_path.to_string()),
|
||||||
|
}, r)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn server_url(&self) -> Result<String, dbus::MethodErr> {
|
fn server_url(&self) -> Result<String, dbus::MethodErr> {
|
||||||
todo!()
|
self.send_event_sync(Event::GetAllSettings)
|
||||||
|
.and_then(|settings| {
|
||||||
|
Ok(settings.server_url.unwrap_or_default())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_server_url(&self, value: String) -> Result<(), dbus::MethodErr> {
|
fn set_server_url(&self, value: String) -> Result<(), dbus::MethodErr> {
|
||||||
todo!()
|
self.send_event_sync(|r|
|
||||||
|
Event::UpdateSettings(Settings {
|
||||||
|
server_url: Some(value),
|
||||||
|
username: None,
|
||||||
|
credential_item: None,
|
||||||
|
}, r)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn username(&self) -> Result<String, dbus::MethodErr> {
|
fn username(&self) -> Result<String, dbus::MethodErr> {
|
||||||
todo!()
|
self.send_event_sync(Event::GetAllSettings)
|
||||||
|
.and_then(|settings| {
|
||||||
|
Ok(settings.username.unwrap_or_default())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_username(&self, value: String) -> Result<(), dbus::MethodErr> {
|
fn set_username(&self, value: String) -> Result<(), dbus::MethodErr> {
|
||||||
todo!()
|
self.send_event_sync(|r|
|
||||||
|
Event::UpdateSettings(Settings {
|
||||||
|
server_url: None,
|
||||||
|
username: Some(value),
|
||||||
|
credential_item: None,
|
||||||
|
}, r)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn credential_item(&self) -> Result<dbus::Path<'static>, dbus::MethodErr> {
|
fn credential_item(&self) -> Result<dbus::Path<'static>, dbus::MethodErr> {
|
||||||
todo!()
|
self.send_event_sync(Event::GetAllSettings)
|
||||||
|
.and_then(|settings| {
|
||||||
|
Ok(settings.credential_item.unwrap_or_default())
|
||||||
|
})
|
||||||
|
.and_then(|item| {
|
||||||
|
Ok(dbus::Path::new(item).unwrap_or_default())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_credential_item(&self, value: dbus::Path<'static>) -> Result<(), dbus::MethodErr> {
|
fn set_credential_item(&self, value: dbus::Path<'static>) -> Result<(), dbus::MethodErr> {
|
||||||
todo!()
|
self.send_event_sync(|r|
|
||||||
|
Event::UpdateSettings(Settings {
|
||||||
|
server_url: None,
|
||||||
|
username: None,
|
||||||
|
credential_item: Some(value.to_string()),
|
||||||
|
}, r)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::Subcommand;
|
use clap::Subcommand;
|
||||||
use dbus::blocking::{Connection, Proxy};
|
use dbus::blocking::{Connection, Proxy};
|
||||||
|
use crate::printers::{ConversationPrinter, MessagePrinter};
|
||||||
|
|
||||||
const DBUS_NAME: &str = "net.buzzert.kordophonecd";
|
const DBUS_NAME: &str = "net.buzzert.kordophonecd";
|
||||||
const DBUS_PATH: &str = "/net/buzzert/kordophonecd/daemon";
|
const DBUS_PATH: &str = "/net/buzzert/kordophonecd/daemon";
|
||||||
@@ -11,6 +12,7 @@ mod dbus_interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
use dbus_interface::NetBuzzertKordophoneRepository as KordophoneRepository;
|
use dbus_interface::NetBuzzertKordophoneRepository as KordophoneRepository;
|
||||||
|
use dbus_interface::NetBuzzertKordophoneSettings as KordophoneSettings;
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
#[derive(Subcommand)]
|
||||||
pub enum Commands {
|
pub enum Commands {
|
||||||
@@ -22,6 +24,33 @@ pub enum Commands {
|
|||||||
|
|
||||||
/// Prints the server Kordophone version.
|
/// Prints the server Kordophone version.
|
||||||
Version,
|
Version,
|
||||||
|
|
||||||
|
/// Configuration options
|
||||||
|
Config {
|
||||||
|
#[command(subcommand)]
|
||||||
|
command: ConfigCommands,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
pub enum ConfigCommands {
|
||||||
|
/// Prints the current settings.
|
||||||
|
Print,
|
||||||
|
|
||||||
|
/// Sets the server URL.
|
||||||
|
SetServerUrl {
|
||||||
|
url: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Sets the username.
|
||||||
|
SetUsername {
|
||||||
|
username: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Sets the credential item.
|
||||||
|
SetCredentialItem {
|
||||||
|
item: String,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Commands {
|
impl Commands {
|
||||||
@@ -31,6 +60,7 @@ impl Commands {
|
|||||||
Commands::Version => client.print_version().await,
|
Commands::Version => client.print_version().await,
|
||||||
Commands::Conversations => client.print_conversations().await,
|
Commands::Conversations => client.print_conversations().await,
|
||||||
Commands::Sync => client.sync_conversations().await,
|
Commands::Sync => client.sync_conversations().await,
|
||||||
|
Commands::Config { command } => client.config(command).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -58,13 +88,53 @@ impl DaemonCli {
|
|||||||
|
|
||||||
pub async fn print_conversations(&mut self) -> Result<()> {
|
pub async fn print_conversations(&mut self) -> Result<()> {
|
||||||
let conversations = KordophoneRepository::get_conversations(&self.proxy())?;
|
let conversations = KordophoneRepository::get_conversations(&self.proxy())?;
|
||||||
println!("Conversations: {:?}", conversations);
|
println!("Number of conversations: {}", conversations.len());
|
||||||
|
|
||||||
|
for conversation in conversations {
|
||||||
|
println!("{}", ConversationPrinter::new(&conversation.into()));
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn sync_conversations(&mut self) -> Result<()> {
|
pub async fn sync_conversations(&mut self) -> Result<()> {
|
||||||
let success = KordophoneRepository::sync_all_conversations(&self.proxy())?;
|
KordophoneRepository::sync_all_conversations(&self.proxy())
|
||||||
println!("Initiated sync");
|
.map_err(|e| anyhow::anyhow!("Failed to sync conversations: {}", e))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn config(&mut self, cmd: ConfigCommands) -> Result<()> {
|
||||||
|
match cmd {
|
||||||
|
ConfigCommands::Print => self.print_settings().await,
|
||||||
|
ConfigCommands::SetServerUrl { url } => self.set_server_url(url).await,
|
||||||
|
ConfigCommands::SetUsername { username } => self.set_username(username).await,
|
||||||
|
ConfigCommands::SetCredentialItem { item } => self.set_credential_item(item).await,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn print_settings(&mut self) -> Result<()> {
|
||||||
|
let server_url = KordophoneSettings::server_url(&self.proxy())?;
|
||||||
|
let username = KordophoneSettings::username(&self.proxy())?;
|
||||||
|
let credential_item = KordophoneSettings::credential_item(&self.proxy())?;
|
||||||
|
|
||||||
|
println!("Server URL: {}", server_url);
|
||||||
|
println!("Username: {}", username);
|
||||||
|
println!("Credential Item: {}", credential_item);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn set_server_url(&mut self, url: String) -> Result<()> {
|
||||||
|
KordophoneSettings::set_server_url(&self.proxy(), url)
|
||||||
|
.map_err(|e| anyhow::anyhow!("Failed to set server URL: {}", e))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_username(&mut self, username: String) -> Result<()> {
|
||||||
|
KordophoneSettings::set_username(&self.proxy(), username)
|
||||||
|
.map_err(|e| anyhow::anyhow!("Failed to set username: {}", e))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_credential_item(&mut self, item: String) -> Result<()> {
|
||||||
|
KordophoneSettings::set_credential_item(&self.proxy(), item.into())
|
||||||
|
.map_err(|e| anyhow::anyhow!("Failed to set credential item: {}", e))
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
use pretty::RcDoc;
|
use pretty::RcDoc;
|
||||||
|
use dbus::arg::{self, RefArg};
|
||||||
|
|
||||||
pub struct PrintableConversation {
|
pub struct PrintableConversation {
|
||||||
pub guid: String,
|
pub guid: String,
|
||||||
@@ -37,6 +38,25 @@ impl From<kordophone_db::models::Conversation> for PrintableConversation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<arg::PropMap> 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")
|
||||||
|
.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()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct PrintableMessage {
|
pub struct PrintableMessage {
|
||||||
pub guid: String,
|
pub guid: String,
|
||||||
pub date: OffsetDateTime,
|
pub date: OffsetDateTime,
|
||||||
|
|||||||
Reference in New Issue
Block a user