Private
Public Access
1
0

implements settings, conversation dbus encoding

This commit is contained in:
2025-04-27 18:07:58 -07:00
parent 49f8b81b9c
commit cecfd7cd76
11 changed files with 446 additions and 96 deletions

68
Cargo.lock generated
View File

@@ -509,14 +509,14 @@ dependencies = [
[[package]]
name = "env_logger"
version = "0.11.6"
version = "0.11.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0"
checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f"
dependencies = [
"anstream",
"anstyle",
"env_filter",
"humantime",
"jiff",
"log",
]
@@ -712,12 +712,6 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "hyper"
version = "0.14.28"
@@ -806,6 +800,30 @@ version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "js-sys"
version = "0.3.72"
@@ -846,6 +864,7 @@ dependencies = [
"diesel",
"diesel_migrations",
"kordophone",
"log",
"serde",
"time",
"tokio",
@@ -950,9 +969,9 @@ dependencies = [
[[package]]
name = "log"
version = "0.4.25"
version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]]
name = "memchr"
@@ -1141,6 +1160,21 @@ version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "powerfmt"
version = "0.2.0"
@@ -1167,18 +1201,18 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.92"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.36"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
@@ -1422,9 +1456,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "2.0.90"
version = "2.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31"
checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
dependencies = [
"proc-macro2",
"quote",

View File

@@ -11,6 +11,7 @@ chrono = "0.4.38"
diesel = { version = "2.2.6", features = ["chrono", "sqlite", "time"] }
diesel_migrations = { version = "2.2.0", features = ["sqlite"] }
kordophone = { path = "../kordophone" }
log = "0.4.27"
serde = { version = "1.0.215", features = ["derive"] }
time = "0.3.37"
tokio = "1.44.2"

View File

@@ -130,6 +130,53 @@ impl<'a> Repository<'a> {
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>> {
use crate::schema::messages::dsl::*;
use crate::schema::conversation_messages::dsl::*;

View File

@@ -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))
}
#[test]
fn test_database_init() {
#[tokio::test]
async fn test_database_init() {
let _ = Database::new_in_memory().unwrap();
}
#[test]
fn test_add_conversation() {
#[tokio::test]
async fn test_add_conversation() {
let mut db = Database::new_in_memory().unwrap();
db.with_repository(|repository| {
let guid = "test";
@@ -62,11 +62,11 @@ fn test_add_conversation() {
// 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;
}
#[test]
fn test_conversation_participants() {
#[tokio::test]
async fn test_conversation_participants() {
let mut db = Database::new_in_memory().unwrap();
db.with_repository(|repository| {
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;
assert!(participants_vec_equal_ignoring_id(&participants, &read_participants));
});
}).await;
}
#[test]
fn test_all_conversations_with_participants() {
#[tokio::test]
async fn test_all_conversations_with_participants() {
let mut db = Database::new_in_memory().unwrap();
db.with_repository(|repository| {
// 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(&conv2.participants, &participants2));
});
}).await;
}
#[test]
fn test_messages() {
#[tokio::test]
async fn test_messages() {
let mut db = Database::new_in_memory().unwrap();
db.with_repository(|repository| {
// First create a conversation with participants
@@ -185,11 +185,11 @@ fn test_messages() {
} else {
panic!("Expected Remote participant. Got: {:?}", retrieved_message2.sender);
}
});
}).await;
}
#[test]
fn test_message_ordering() {
#[tokio::test]
async fn test_message_ordering() {
let mut db = Database::new_in_memory().unwrap();
db.with_repository(|repository| {
// Create a conversation
@@ -229,11 +229,93 @@ fn test_message_ordering() {
for i in 1..messages.len() {
assert!(messages[i].date > messages[i-1].date);
}
});
}).await;
}
#[test]
fn test_settings() {
#[tokio::test]
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();
db.with_settings(|settings| {
settings.put("test", &"test".to_string()).unwrap();
@@ -244,5 +326,27 @@ fn test_settings() {
let keys = settings.list_keys().unwrap();
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;
}

View File

@@ -13,7 +13,10 @@
'id' (string): Unique identifier
'title' (string): Display name
'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>
</method>

View File

@@ -1,5 +1,6 @@
use tokio::sync::oneshot;
use kordophone_db::models::Conversation;
use crate::daemon::settings::Settings;
pub type Reply<T> = oneshot::Sender<T>;
@@ -13,6 +14,12 @@ pub enum Event {
/// Returns all known conversations from the database.
GetAllConversations(Reply<Vec<Conversation>>),
/// Returns all known settings from the database.
GetAllSettings(Reply<Settings>),
/// Update settings in the database.
UpdateSettings(Settings, Reply<()>),
}

View File

@@ -50,6 +50,10 @@ impl TokenStore for DatabaseTokenStore {
}
}
mod target {
pub static SYNC: &str = "sync";
}
pub struct Daemon {
pub event_sender: Sender<Event>,
event_receiver: Receiver<Event>,
@@ -110,6 +114,25 @@ impl Daemon {
let conversations = self.get_conversations().await;
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<()> {
log::info!("Starting conversation sync");
log::info!(target: target::SYNC, "Starting conversation sync");
// Get client from the database
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
let mut client = Self::get_client_impl(database.clone()).await?;
// Fetch conversations from server
let fetched_conversations = client.get_conversations().await?;
@@ -152,6 +152,7 @@ impl Daemon {
.collect();
// Process each conversation
let num_conversations = db_conversations.len();
for conversation in db_conversations {
let conversation_id = conversation.guid.clone();
@@ -159,21 +160,18 @@ impl Daemon {
database.with_repository(|r| r.insert_conversation(conversation)).await?;
// 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 db_messages: Vec<kordophone_db::models::Message> = messages.into_iter()
.map(|m| kordophone_db::models::Message::from(m))
.collect();
// Insert each message
database.with_repository(|r| -> Result<()> {
for message in db_messages {
r.insert_message(&conversation_id, message)?;
}
Ok(())
}).await?;
log::info!(target: target::SYNC, "Inserting {} messages for conversation {}", db_messages.len(), conversation_id);
database.with_repository(|r| r.insert_messages(&conversation_id, db_messages)).await?;
}
log::info!(target: target::SYNC, "Synchronized {} conversations", num_conversations);
Ok(())
}
@@ -185,8 +183,16 @@ impl Daemon {
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>> {
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)
).await?;
@@ -205,7 +211,7 @@ impl Daemon {
),
_ => None,
},
DatabaseTokenStore { database: self.database.clone() }
DatabaseTokenStore { database: database.clone() }
);
Ok(client)

View File

@@ -16,9 +16,9 @@ pub struct Settings {
impl Settings {
pub fn from_db(db_settings: &mut DbSettings) -> Result<Self> {
let server_url = db_settings.get(keys::SERVER_URL)?;
let username = db_settings.get(keys::USERNAME)?;
let credential_item = db_settings.get(keys::CREDENTIAL_ITEM)?;
let server_url: Option<String> = db_settings.get(keys::SERVER_URL)?;
let username: Option<String> = db_settings.get(keys::USERNAME)?;
let credential_item: Option<String> = db_settings.get(keys::CREDENTIAL_ITEM)?;
Ok(Self {
server_url,
@@ -28,9 +28,25 @@ impl Settings {
}
pub fn save(&self, db_settings: &mut DbSettings) -> Result<()> {
db_settings.put(keys::SERVER_URL, &self.server_url)?;
db_settings.put(keys::USERNAME, &self.username)?;
db_settings.put(keys::CREDENTIAL_ITEM, &self.credential_item)?;
if let Some(server_url) = &self.server_url {
db_settings.put(keys::SERVER_URL, &server_url)?;
}
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(())
}
}
impl Default for Settings {
fn default() -> Self {
Self {
server_url: None,
username: None,
credential_item: None,
}
}
}

View File

@@ -1,17 +1,14 @@
use dbus::arg;
use dbus_tree::MethodErr;
use std::sync::Arc;
use tokio::sync::{Mutex, MutexGuard};
use tokio::sync::mpsc;
use std::future::Future;
use std::thread;
use tokio::sync::oneshot;
use tokio::sync::mpsc;
use futures_util::future::FutureExt;
use crate::daemon::{
Daemon,
DaemonResult,
events::{Event, Reply},
settings::Settings,
};
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("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
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();
Ok(result)
@@ -77,35 +77,77 @@ impl DbusRepository for ServerImpl {
impl DbusSettings for ServerImpl {
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> {
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> {
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> {
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> {
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> {
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> {
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> {
todo!()
self.send_event_sync(|r|
Event::UpdateSettings(Settings {
server_url: None,
username: None,
credential_item: Some(value.to_string()),
}, r)
)
}
}

View File

@@ -1,6 +1,7 @@
use anyhow::Result;
use clap::Subcommand;
use dbus::blocking::{Connection, Proxy};
use crate::printers::{ConversationPrinter, MessagePrinter};
const DBUS_NAME: &str = "net.buzzert.kordophonecd";
const DBUS_PATH: &str = "/net/buzzert/kordophonecd/daemon";
@@ -11,6 +12,7 @@ mod dbus_interface {
}
use dbus_interface::NetBuzzertKordophoneRepository as KordophoneRepository;
use dbus_interface::NetBuzzertKordophoneSettings as KordophoneSettings;
#[derive(Subcommand)]
pub enum Commands {
@@ -22,6 +24,33 @@ pub enum Commands {
/// Prints the server Kordophone 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 {
@@ -31,6 +60,7 @@ impl Commands {
Commands::Version => client.print_version().await,
Commands::Conversations => client.print_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<()> {
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(())
}
pub async fn sync_conversations(&mut self) -> Result<()> {
let success = KordophoneRepository::sync_all_conversations(&self.proxy())?;
println!("Initiated sync");
KordophoneRepository::sync_all_conversations(&self.proxy())
.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(())
}
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))
}
}

View File

@@ -1,6 +1,7 @@
use std::fmt::Display;
use time::OffsetDateTime;
use pretty::RcDoc;
use dbus::arg::{self, RefArg};
pub struct PrintableConversation {
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 guid: String,
pub date: OffsetDateTime,