Started working on contact resolution
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -1063,6 +1063,7 @@ dependencies = [
|
|||||||
"kordophone",
|
"kordophone",
|
||||||
"kordophone-db",
|
"kordophone-db",
|
||||||
"log",
|
"log",
|
||||||
|
"once_cell",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.12",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
-- Revert participants table to remove contact_id column
|
||||||
|
-- SQLite does not support DROP COLUMN directly, so we recreate the table without contact_id
|
||||||
|
PRAGMA foreign_keys=off;
|
||||||
|
CREATE TABLE participants_backup (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY,
|
||||||
|
display_name TEXT,
|
||||||
|
is_me BOOL NOT NULL
|
||||||
|
);
|
||||||
|
INSERT INTO participants_backup (id, display_name, is_me)
|
||||||
|
SELECT id, display_name, is_me
|
||||||
|
FROM participants;
|
||||||
|
DROP TABLE participants;
|
||||||
|
ALTER TABLE participants_backup RENAME TO participants;
|
||||||
|
PRAGMA foreign_keys=on;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- Add contact_id column to participants to store an external contact identifier (e.g., Folks ID)
|
||||||
|
ALTER TABLE participants ADD COLUMN contact_id TEXT;
|
||||||
@@ -8,6 +8,7 @@ pub struct Record {
|
|||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub display_name: Option<String>,
|
pub display_name: Option<String>,
|
||||||
pub is_me: bool,
|
pub is_me: bool,
|
||||||
|
pub contact_id: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Insertable)]
|
#[derive(Insertable)]
|
||||||
@@ -15,6 +16,7 @@ pub struct Record {
|
|||||||
pub struct InsertableRecord {
|
pub struct InsertableRecord {
|
||||||
pub display_name: Option<String>,
|
pub display_name: Option<String>,
|
||||||
pub is_me: bool,
|
pub is_me: bool,
|
||||||
|
pub contact_id: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Participant> for InsertableRecord {
|
impl From<Participant> for InsertableRecord {
|
||||||
@@ -23,10 +25,12 @@ impl From<Participant> for InsertableRecord {
|
|||||||
Participant::Me => InsertableRecord {
|
Participant::Me => InsertableRecord {
|
||||||
display_name: None,
|
display_name: None,
|
||||||
is_me: true,
|
is_me: true,
|
||||||
|
contact_id: None,
|
||||||
},
|
},
|
||||||
Participant::Remote { display_name, .. } => InsertableRecord {
|
Participant::Remote { display_name, contact_id, .. } => InsertableRecord {
|
||||||
display_name: Some(display_name),
|
display_name: Some(display_name),
|
||||||
is_me: false,
|
is_me: false,
|
||||||
|
contact_id,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -50,6 +54,7 @@ impl From<Record> for Participant {
|
|||||||
Participant::Remote {
|
Participant::Remote {
|
||||||
id: Some(record.id),
|
id: Some(record.id),
|
||||||
display_name: record.display_name.unwrap_or_default(),
|
display_name: record.display_name.unwrap_or_default(),
|
||||||
|
contact_id: record.contact_id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -62,11 +67,13 @@ impl From<Participant> for Record {
|
|||||||
id: 0, // This will be set by the database
|
id: 0, // This will be set by the database
|
||||||
display_name: None,
|
display_name: None,
|
||||||
is_me: true,
|
is_me: true,
|
||||||
|
contact_id: None,
|
||||||
},
|
},
|
||||||
Participant::Remote { display_name, .. } => Record {
|
Participant::Remote { display_name, contact_id, .. } => Record {
|
||||||
id: 0, // This will be set by the database
|
id: 0, // This will be set by the database
|
||||||
display_name: Some(display_name),
|
display_name: Some(display_name),
|
||||||
is_me: false,
|
is_me: false,
|
||||||
|
contact_id,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ impl From<kordophone::model::Message> for Message {
|
|||||||
Some(sender) => Participant::Remote {
|
Some(sender) => Participant::Remote {
|
||||||
id: None,
|
id: None,
|
||||||
display_name: sender,
|
display_name: sender,
|
||||||
|
contact_id: None,
|
||||||
},
|
},
|
||||||
None => Participant::Me,
|
None => Participant::Me,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ pub enum Participant {
|
|||||||
Remote {
|
Remote {
|
||||||
id: Option<i32>,
|
id: Option<i32>,
|
||||||
display_name: String,
|
display_name: String,
|
||||||
|
contact_id: Option<String>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -12,6 +13,7 @@ impl From<String> for Participant {
|
|||||||
Participant::Remote {
|
Participant::Remote {
|
||||||
id: None,
|
id: None,
|
||||||
display_name,
|
display_name,
|
||||||
|
contact_id: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -21,6 +23,7 @@ impl From<&str> for Participant {
|
|||||||
Participant::Remote {
|
Participant::Remote {
|
||||||
id: None,
|
id: None,
|
||||||
display_name: display_name.to_string(),
|
display_name: display_name.to_string(),
|
||||||
|
contact_id: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,8 +13,7 @@ use crate::{
|
|||||||
},
|
},
|
||||||
Conversation, Message, Participant,
|
Conversation, Message, Participant,
|
||||||
},
|
},
|
||||||
schema,
|
schema, target,
|
||||||
target,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Repository<'a> {
|
pub struct Repository<'a> {
|
||||||
@@ -195,6 +194,7 @@ impl<'a> Repository<'a> {
|
|||||||
let new_participant = InsertableParticipantRecord {
|
let new_participant = InsertableParticipantRecord {
|
||||||
display_name: Some(display_name.clone()),
|
display_name: Some(display_name.clone()),
|
||||||
is_me: false,
|
is_me: false,
|
||||||
|
contact_id: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
diesel::insert_into(participants_dsl::participants)
|
diesel::insert_into(participants_dsl::participants)
|
||||||
@@ -371,11 +371,27 @@ impl<'a> Repository<'a> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Update the contact_id for an existing participant record.
|
||||||
|
pub fn update_participant_contact(
|
||||||
|
&mut self,
|
||||||
|
participant_db_id: i32,
|
||||||
|
new_contact_id: &str,
|
||||||
|
) -> Result<()> {
|
||||||
|
use crate::schema::participants::dsl::*;
|
||||||
|
|
||||||
|
log::debug!(target: target::REPOSITORY, "Updating participant contact id {} => {}", participant_db_id, new_contact_id);
|
||||||
|
diesel::update(participants.filter(id.eq(participant_db_id)))
|
||||||
|
.set(contact_id.eq(Some(new_contact_id.to_string())))
|
||||||
|
.execute(self.connection)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn get_or_create_participant(&mut self, participant: &Participant) -> Option<i32> {
|
fn get_or_create_participant(&mut self, participant: &Participant) -> Option<i32> {
|
||||||
match participant {
|
match participant {
|
||||||
Participant::Me => None,
|
Participant::Me => None,
|
||||||
Participant::Remote {
|
Participant::Remote {
|
||||||
display_name: p_name,
|
display_name: p_name,
|
||||||
|
contact_id: c_id,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
use crate::schema::participants::dsl::*;
|
use crate::schema::participants::dsl::*;
|
||||||
@@ -393,6 +409,7 @@ impl<'a> Repository<'a> {
|
|||||||
let participant_record = InsertableParticipantRecord {
|
let participant_record = InsertableParticipantRecord {
|
||||||
display_name: Some(participant.display_name()),
|
display_name: Some(participant.display_name()),
|
||||||
is_me: false,
|
is_me: false,
|
||||||
|
contact_id: c_id.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
diesel::insert_into(participants)
|
diesel::insert_into(participants)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ diesel::table! {
|
|||||||
id -> Integer,
|
id -> Integer,
|
||||||
display_name -> Nullable<Text>,
|
display_name -> Nullable<Text>,
|
||||||
is_me -> Bool,
|
is_me -> Bool,
|
||||||
|
contact_id -> Nullable<Text>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ edition = "2021"
|
|||||||
anyhow = "1.0.98"
|
anyhow = "1.0.98"
|
||||||
async-trait = "0.1.88"
|
async-trait = "0.1.88"
|
||||||
chrono = "0.4.38"
|
chrono = "0.4.38"
|
||||||
dbus = "0.9.7"
|
dbus = { version = "0.9.7", features = ["futures"] }
|
||||||
dbus-crossroads = "0.5.2"
|
dbus-crossroads = "0.5.2"
|
||||||
dbus-tokio = "0.7.6"
|
dbus-tokio = "0.7.6"
|
||||||
dbus-tree = "0.9.2"
|
dbus-tree = "0.9.2"
|
||||||
@@ -23,6 +23,7 @@ thiserror = "2.0.12"
|
|||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
tokio-condvar = "0.3.0"
|
tokio-condvar = "0.3.0"
|
||||||
uuid = "1.16.0"
|
uuid = "1.16.0"
|
||||||
|
once_cell = "1.19.0"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
dbus-codegen = "0.10.0"
|
dbus-codegen = "0.10.0"
|
||||||
|
|||||||
229
kordophoned/src/daemon/contact_resolver/eds.rs
Normal file
229
kordophoned/src/daemon/contact_resolver/eds.rs
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
use super::ContactResolverBackend;
|
||||||
|
use dbus::blocking::Connection;
|
||||||
|
use dbus::arg::{RefArg, Variant};
|
||||||
|
use once_cell::sync::OnceCell;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
pub struct EDSContactResolverBackend;
|
||||||
|
|
||||||
|
// Cache the UID of the default local address book so we do not have to scan
|
||||||
|
// all sources over and over again. Discovering the address book requires a
|
||||||
|
// D-Bus round-trip that we would rather avoid on every lookup.
|
||||||
|
static ADDRESS_BOOK_SOURCE_UID: OnceCell<String> = OnceCell::new();
|
||||||
|
|
||||||
|
/// Helper that returns a blocking D-Bus session connection. Creating the
|
||||||
|
/// connection is cheap (<1 ms) but we still keep it around because the
|
||||||
|
/// underlying socket is re-used by the dbus crate.
|
||||||
|
fn new_session_connection() -> Result<Connection, dbus::Error> {
|
||||||
|
Connection::new_session()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Scan Evolution-Data-Server sources to find a suitable address-book source
|
||||||
|
/// UID. The implementation mirrors what `gdbus introspect` reveals for the
|
||||||
|
/// EDS interfaces. We search all `org.gnome.evolution.dataserver.Source`
|
||||||
|
/// objects and pick the first one that advertises the `[Address Book]` section
|
||||||
|
/// with a `BackendName=` entry in its INI-style `Data` property.
|
||||||
|
fn ensure_address_book_uid(conn: &Connection) -> anyhow::Result<String> {
|
||||||
|
if let Some(uid) = ADDRESS_BOOK_SOURCE_UID.get() {
|
||||||
|
return Ok(uid.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
let source_manager_proxy = conn.with_proxy(
|
||||||
|
"org.gnome.evolution.dataserver.Sources5",
|
||||||
|
"/org/gnome/evolution/dataserver/SourceManager",
|
||||||
|
Duration::from_secs(5),
|
||||||
|
);
|
||||||
|
|
||||||
|
// The GetManagedObjects reply is the usual ObjectManager map.
|
||||||
|
let (managed_objects,): (
|
||||||
|
HashMap<
|
||||||
|
dbus::Path<'static>,
|
||||||
|
HashMap<String, HashMap<String, Variant<Box<dyn RefArg>>>>,
|
||||||
|
>,
|
||||||
|
) = source_manager_proxy.method_call(
|
||||||
|
"org.freedesktop.DBus.ObjectManager",
|
||||||
|
"GetManagedObjects",
|
||||||
|
(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let uid = managed_objects
|
||||||
|
.values()
|
||||||
|
.filter_map(|ifaces| ifaces.get("org.gnome.evolution.dataserver.Source"))
|
||||||
|
.filter_map(|props| {
|
||||||
|
let uid = props.get("UID")?.as_str()?;
|
||||||
|
let data = props.get("Data")?.as_str()?;
|
||||||
|
if data_contains_address_book_backend(data) {
|
||||||
|
Some(uid.to_owned())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("No address book source found"))?;
|
||||||
|
|
||||||
|
// Remember for future look-ups.
|
||||||
|
log::debug!("EDS resolver: found address book source UID: {}", uid);
|
||||||
|
let _ = ADDRESS_BOOK_SOURCE_UID.set(uid.clone());
|
||||||
|
Ok(uid)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data_contains_address_book_backend(data: &str) -> bool {
|
||||||
|
let mut in_address_book_section = false;
|
||||||
|
for line in data.lines() {
|
||||||
|
let trimmed = line.trim();
|
||||||
|
if trimmed.starts_with('[') && trimmed.ends_with(']') {
|
||||||
|
in_address_book_section = trimmed == "[Address Book]";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if in_address_book_section && trimmed.starts_with("BackendName=") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Open the Evolution address book referenced by `source_uid` and return the
|
||||||
|
/// pair `(object_path, bus_name)` that identifies the newly created D-Bus
|
||||||
|
/// proxy.
|
||||||
|
fn open_address_book(
|
||||||
|
conn: &Connection,
|
||||||
|
source_uid: &str,
|
||||||
|
) -> anyhow::Result<(String, String)> {
|
||||||
|
let factory_proxy = conn.with_proxy(
|
||||||
|
"org.gnome.evolution.dataserver.AddressBook10",
|
||||||
|
"/org/gnome/evolution/dataserver/AddressBookFactory",
|
||||||
|
Duration::from_secs(60),
|
||||||
|
);
|
||||||
|
|
||||||
|
let (object_path, bus_name): (String, String) = factory_proxy.method_call(
|
||||||
|
"org.gnome.evolution.dataserver.AddressBookFactory",
|
||||||
|
"OpenAddressBook",
|
||||||
|
(source_uid.to_owned(),),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok((object_path, bus_name))
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContactResolverBackend for EDSContactResolverBackend {
|
||||||
|
type ContactID = String;
|
||||||
|
|
||||||
|
fn resolve_contact_id(&self, address: &str) -> Option<Self::ContactID> {
|
||||||
|
// Only email addresses are supported for now. We fall back to NONE on
|
||||||
|
// any error to keep the resolver infallible for callers.
|
||||||
|
let conn = match new_session_connection() {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => {
|
||||||
|
log::debug!("EDS resolver: failed to open session D-Bus: {}", e);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let source_uid = match ensure_address_book_uid(&conn) {
|
||||||
|
Ok(u) => u,
|
||||||
|
Err(e) => {
|
||||||
|
log::debug!("EDS resolver: could not determine address-book UID: {}", e);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let (object_path, bus_name) = match open_address_book(&conn, &source_uid) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => {
|
||||||
|
log::debug!("EDS resolver: failed to open address book: {}", e);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let address_book_proxy = conn.with_proxy(bus_name, object_path, Duration::from_secs(60));
|
||||||
|
|
||||||
|
let filter = if address.contains('@') {
|
||||||
|
format!("(is \"email\" \"{}\")", address)
|
||||||
|
} else {
|
||||||
|
// Remove country code, if present
|
||||||
|
let address = address.replace("+", "")
|
||||||
|
.chars()
|
||||||
|
.skip_while(|c| c.is_numeric() || *c == '(' || *c == ')')
|
||||||
|
.collect::<String>();
|
||||||
|
|
||||||
|
// Remove any remaining non-numeric characters
|
||||||
|
let address = address.chars()
|
||||||
|
.filter(|c| c.is_numeric())
|
||||||
|
.collect::<String>();
|
||||||
|
|
||||||
|
format!("(is \"phone\" \"{}\")", address)
|
||||||
|
};
|
||||||
|
|
||||||
|
let uids_result: Result<(Vec<String>,), _> = address_book_proxy.method_call(
|
||||||
|
"org.gnome.evolution.dataserver.AddressBook",
|
||||||
|
"GetContactListUids",
|
||||||
|
(filter,),
|
||||||
|
);
|
||||||
|
|
||||||
|
let (uids,) = match uids_result {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => {
|
||||||
|
log::debug!("EDS resolver: GetContactListUids failed: {}", e);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
uids.into_iter().next()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_contact_display_name(&self, contact_id: &Self::ContactID) -> Option<String> {
|
||||||
|
let conn = match new_session_connection() {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => {
|
||||||
|
log::debug!("EDS resolver: failed to open session D-Bus: {}", e);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let source_uid = match ensure_address_book_uid(&conn) {
|
||||||
|
Ok(u) => u,
|
||||||
|
Err(e) => {
|
||||||
|
log::debug!("EDS resolver: could not determine address-book UID: {}", e);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let (object_path, bus_name) = match open_address_book(&conn, &source_uid) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => {
|
||||||
|
log::debug!("EDS resolver: failed to open address book: {}", e);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let address_book_proxy = conn.with_proxy(bus_name, object_path, Duration::from_secs(60));
|
||||||
|
|
||||||
|
let vcard_result: Result<(String,), _> = address_book_proxy.method_call(
|
||||||
|
"org.gnome.evolution.dataserver.AddressBook",
|
||||||
|
"GetContact",
|
||||||
|
(contact_id.clone(),),
|
||||||
|
);
|
||||||
|
|
||||||
|
let (vcard,) = match vcard_result {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => {
|
||||||
|
log::debug!("EDS resolver: GetContact failed: {}", e);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for line in vcard.lines() {
|
||||||
|
if let Some(rest) = line.strip_prefix("FN:") {
|
||||||
|
return Some(rest.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for EDSContactResolverBackend {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
}
|
||||||
46
kordophoned/src/daemon/contact_resolver/mod.rs
Normal file
46
kordophoned/src/daemon/contact_resolver/mod.rs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
pub mod eds;
|
||||||
|
pub use eds::EDSContactResolverBackend;
|
||||||
|
|
||||||
|
pub trait ContactResolverBackend {
|
||||||
|
type ContactID;
|
||||||
|
|
||||||
|
fn resolve_contact_id(&self, address: &str) -> Option<Self::ContactID>;
|
||||||
|
fn get_contact_display_name(&self, contact_id: &Self::ContactID) -> Option<String>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type AnyContactID = String;
|
||||||
|
|
||||||
|
pub struct ContactResolver<T: ContactResolverBackend> {
|
||||||
|
backend: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ContactResolverBackend> ContactResolver<T>
|
||||||
|
where
|
||||||
|
T::ContactID: From<AnyContactID>,
|
||||||
|
T::ContactID: Into<AnyContactID>,
|
||||||
|
T: Default,
|
||||||
|
{
|
||||||
|
pub fn new(backend: T) -> Self {
|
||||||
|
Self { backend }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resolve_contact_id(&self, address: &str) -> Option<AnyContactID> {
|
||||||
|
self.backend.resolve_contact_id(address).map(|id| id.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_contact_display_name(&self, contact_id: &AnyContactID) -> Option<String> {
|
||||||
|
let backend_contact_id: T::ContactID = T::ContactID::from((*contact_id).clone());
|
||||||
|
self.backend.get_contact_display_name(&backend_contact_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ContactResolverBackend> Default for ContactResolver<T>
|
||||||
|
where
|
||||||
|
T::ContactID: From<AnyContactID>,
|
||||||
|
T::ContactID: Into<AnyContactID>,
|
||||||
|
T: Default,
|
||||||
|
{
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new(T::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -49,6 +49,12 @@ mod attachment_store;
|
|||||||
pub use attachment_store::AttachmentStore;
|
pub use attachment_store::AttachmentStore;
|
||||||
pub use attachment_store::AttachmentStoreEvent;
|
pub use attachment_store::AttachmentStoreEvent;
|
||||||
|
|
||||||
|
pub mod contact_resolver;
|
||||||
|
use contact_resolver::ContactResolver;
|
||||||
|
use contact_resolver::EDSContactResolverBackend;
|
||||||
|
|
||||||
|
use kordophone_db::models::participant::Participant as DbParticipant;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum DaemonError {
|
pub enum DaemonError {
|
||||||
#[error("Client Not Configured")]
|
#[error("Client Not Configured")]
|
||||||
@@ -501,10 +507,34 @@ impl Daemon {
|
|||||||
|
|
||||||
// Insert each conversation
|
// Insert each conversation
|
||||||
let num_conversations = db_conversations.len();
|
let num_conversations = db_conversations.len();
|
||||||
|
let contact_resolver = ContactResolver::new(EDSContactResolverBackend::default());
|
||||||
for conversation in db_conversations {
|
for conversation in db_conversations {
|
||||||
|
// Insert or update conversation and its participants
|
||||||
database
|
database
|
||||||
.with_repository(|r| r.insert_conversation(conversation))
|
.with_repository(|r| r.insert_conversation(conversation.clone()))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
// Resolve any new participants via the contact resolver and store their contact_id
|
||||||
|
log::trace!(target: target::SYNC, "Resolving participants for conversation: {}", conversation.guid);
|
||||||
|
let guid = conversation.guid.clone();
|
||||||
|
if let Some(saved) = database
|
||||||
|
.with_repository(|r| r.get_conversation_by_guid(&guid))
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
for p in &saved.participants {
|
||||||
|
if let DbParticipant::Remote { id: Some(pid), display_name, contact_id: None } = p {
|
||||||
|
log::trace!(target: target::SYNC, "Resolving contact id for participant: {}", display_name);
|
||||||
|
if let Some(contact) = contact_resolver.resolve_contact_id(display_name) {
|
||||||
|
log::trace!(target: target::SYNC, "Resolved contact id for participant: {}", contact);
|
||||||
|
let _ = database
|
||||||
|
.with_repository(|r| r.update_participant_contact(*pid, &contact))
|
||||||
|
.await;
|
||||||
|
} else {
|
||||||
|
log::trace!(target: target::SYNC, "No contact id found for participant: {}", display_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send conversations updated signal
|
// Send conversations updated signal
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ pub enum Participant {
|
|||||||
Remote {
|
Remote {
|
||||||
id: Option<i32>,
|
id: Option<i32>,
|
||||||
display_name: String,
|
display_name: String,
|
||||||
|
contact_id: Option<String>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,6 +22,7 @@ impl From<String> for Participant {
|
|||||||
Participant::Remote {
|
Participant::Remote {
|
||||||
id: None,
|
id: None,
|
||||||
display_name,
|
display_name,
|
||||||
|
contact_id: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -30,6 +32,7 @@ impl From<&str> for Participant {
|
|||||||
Participant::Remote {
|
Participant::Remote {
|
||||||
id: None,
|
id: None,
|
||||||
display_name: display_name.to_string(),
|
display_name: display_name.to_string(),
|
||||||
|
contact_id: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,8 +41,8 @@ impl From<kordophone_db::models::Participant> for Participant {
|
|||||||
fn from(participant: kordophone_db::models::Participant) -> Self {
|
fn from(participant: kordophone_db::models::Participant) -> Self {
|
||||||
match participant {
|
match participant {
|
||||||
kordophone_db::models::Participant::Me => Participant::Me,
|
kordophone_db::models::Participant::Me => Participant::Me,
|
||||||
kordophone_db::models::Participant::Remote { id, display_name } => {
|
kordophone_db::models::Participant::Remote { id, display_name, contact_id } => {
|
||||||
Participant::Remote { id, display_name }
|
Participant::Remote { id, display_name, contact_id }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -107,8 +110,8 @@ impl From<Message> for kordophone_db::models::Message {
|
|||||||
id: message.id,
|
id: message.id,
|
||||||
sender: match message.sender {
|
sender: match message.sender {
|
||||||
Participant::Me => kordophone_db::models::Participant::Me,
|
Participant::Me => kordophone_db::models::Participant::Me,
|
||||||
Participant::Remote { id, display_name } => {
|
Participant::Remote { id, display_name, contact_id } => {
|
||||||
kordophone_db::models::Participant::Remote { id, display_name }
|
kordophone_db::models::Participant::Remote { id, display_name, contact_id }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
text: message.text,
|
text: message.text,
|
||||||
@@ -145,6 +148,7 @@ impl From<kordophone::model::Message> for Message {
|
|||||||
Some(sender) => Participant::Remote {
|
Some(sender) => Participant::Remote {
|
||||||
id: None,
|
id: None,
|
||||||
display_name: sender,
|
display_name: sender,
|
||||||
|
contact_id: None,
|
||||||
},
|
},
|
||||||
None => Participant::Me,
|
None => Participant::Me,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -9,8 +9,11 @@ use crate::daemon::{
|
|||||||
settings::Settings,
|
settings::Settings,
|
||||||
signals::Signal,
|
signals::Signal,
|
||||||
DaemonResult,
|
DaemonResult,
|
||||||
|
contact_resolver::{ContactResolver, EDSContactResolverBackend},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use kordophone_db::models::participant::Participant;
|
||||||
|
|
||||||
use crate::dbus::endpoint::DbusRegistry;
|
use crate::dbus::endpoint::DbusRegistry;
|
||||||
use crate::dbus::interface;
|
use crate::dbus::interface;
|
||||||
use crate::dbus::interface::signals as DbusSignals;
|
use crate::dbus::interface::signals as DbusSignals;
|
||||||
@@ -167,6 +170,24 @@ impl DBusAgent {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.map_err(|e| MethodErr::failed(&format!("Daemon error: {}", e)))
|
.map_err(|e| MethodErr::failed(&format!("Daemon error: {}", e)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn resolve_participant_display_name(&self, participant: &Participant) -> String {
|
||||||
|
let resolver = ContactResolver::new(EDSContactResolverBackend::default());
|
||||||
|
match participant {
|
||||||
|
// Me (we should use a special string here...)
|
||||||
|
Participant::Me => "(Me)".to_string(),
|
||||||
|
|
||||||
|
// Remote participant with a resolved contact_id
|
||||||
|
Participant::Remote { display_name, contact_id: Some(contact_id), .. } => {
|
||||||
|
resolver.get_contact_display_name(contact_id).unwrap_or_else(|| display_name.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remote participant without a resolved contact_id
|
||||||
|
Participant::Remote { display_name, .. } => {
|
||||||
|
display_name.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -207,7 +228,7 @@ impl DbusRepository for DBusAgent {
|
|||||||
arg::Variant(Box::new(
|
arg::Variant(Box::new(
|
||||||
conv.participants
|
conv.participants
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|p| p.display_name())
|
.map(|p| self.resolve_participant_display_name(&p))
|
||||||
.collect::<Vec<String>>(),
|
.collect::<Vec<String>>(),
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
@@ -221,6 +242,7 @@ impl DbusRepository for DBusAgent {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn sync_conversation_list(&mut self) -> Result<(), MethodErr> {
|
fn sync_conversation_list(&mut self) -> Result<(), MethodErr> {
|
||||||
self.send_event_sync(Event::SyncConversationList)
|
self.send_event_sync(Event::SyncConversationList)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user