Skip to content

Commit

Permalink
populate name cache on startup
Browse files Browse the repository at this point in the history
This is a preparation for the presage changes that will require an async
operation to lookup the name of a user.
  • Loading branch information
boxdot committed Nov 10, 2024
1 parent 6bcc592 commit 021e89c
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 38 deletions.
91 changes: 55 additions & 36 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ use image::codecs::png::PngEncoder;
use image::{ImageBuffer, ImageEncoder, Rgba};
use itertools::Itertools;
use notify_rust::Notification;
use phonenumber::Mode;
use presage::libsignal_service::content::{Content, ContentBody, Metadata};
use presage::libsignal_service::sender::AttachmentSpec;
use presage::libsignal_service::ServiceAddress;
Expand Down Expand Up @@ -141,6 +140,23 @@ impl App {
Ok((app, event_rx))
}

/// Resolve and cache all user names for the known user channels
pub fn populate_names_cache(&self) {
let mut names_cache = BTreeMap::new();
for user_id in self
.storage
.channels()
.filter_map(|channel| channel.id.user())
{
if let Some(name) = self.resolve_name(user_id) {
names_cache.insert(user_id, name);
}
}
let mut cache = self.names_cache.take().unwrap_or_default();
cache.extend(names_cache);
self.names_cache.replace(Some(cache));
}

pub fn get_input(&mut self) -> &mut Input {
if self.select_channel.is_shown {
&mut self.select_channel.input
Expand Down Expand Up @@ -170,52 +186,55 @@ impl App {
}
}

/// Resolves name of a user by their id
///
/// The resolution is done from the following places:
///
/// 1. signal's profile name storage
/// 2. signal's contacts storage
/// 3. internal gurk's user name table
fn resolve_name(&self, user_id: Uuid) -> Option<String> {
if let Some(name) = self.signal_manager.profile_name(user_id) {
debug!(name, "resolved name as profile name");
Some(name)
} else if let Some(contact) = self.signal_manager.contact(user_id) {
debug!(name = contact.name, "resolved name from contacts");
Some(contact.name)
} else if let Some(name) = self
.storage
.name(user_id)
.filter(|name| !name.trim().is_empty())
{
debug!(%name, "resolved name from storage");
Some(name.into_owned())
} else {
None
}
}

// Resolves name of a user by their id
//
// The resolution is done in the following way:
//
// 1. It's us => name from config
// 2. User id is in presage's signal manager (that is, it is a known contact from our address
// book) => use it,
// 3. User id is in the gurk's user name table (custom name) => use it,
// 4. give up with UUID as user name
pub fn name_by_id(&self, id: Uuid) -> String {
if self.user_id == id {
// it's me
return self.config.user.name.clone();
};
self.name_by_id_cached(id, |id| {
if let Some(name) = self.signal_manager.profile_name(id) {
return name;
}
if let Some(name) = self.signal_manager.contact(id).and_then(|contact| {
if !contact.name.trim().is_empty() {
Some(contact.name)
} else {
contact
.phone_number
.map(|p| p.format().mode(Mode::E164).to_string())
}
}) {
return name;
}
if let Some(name) = self.storage.name(id).filter(|name| !name.trim().is_empty()) {
// user should be at least known via their profile or phone number
return name.into_owned();
}
// give up
id.to_string()
})
self.config.user.name.clone()
} else {
self.name_by_id_or_cache(id, |id| self.resolve_name(id))
}
}

fn name_by_id_cached(&self, id: Uuid, on_miss: impl FnOnce(Uuid) -> String) -> String {
fn name_by_id_or_cache(
&self,
id: Uuid,
on_miss: impl FnOnce(Uuid) -> Option<String>,
) -> String {
let mut cache = self.names_cache.take().unwrap_or_default();
let name = if let Some(name) = cache.get(&id).cloned() {
name
} else {
let name = on_miss(id);
} else if let Some(name) = on_miss(id) {
cache.insert(id, name.clone());
name
} else {
id.to_string()
};
self.names_cache.replace(Some(cache));
name
Expand Down
7 changes: 7 additions & 0 deletions src/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,13 @@ impl ChannelId {
Ok(Self::Group(group_id))
}

pub(crate) fn user(&self) -> Option<Uuid> {
match self {
ChannelId::User(uuid) => Some(*uuid),
_ => None,
}
}

pub(crate) fn is_user(&self) -> bool {
matches!(self, ChannelId::User(_))
}
Expand Down
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ async fn run_single_threaded(relink: bool) -> anyhow::Result<()> {
sync_from_signal(&*signal_manager, &mut *storage);

let (mut app, mut app_events) = App::try_new(config, signal_manager.clone_boxed(), storage)?;
app.populate_names_cache();

// sync task can be only spawned after we start to listen to message, because it relies on
// message sender to be running
Expand Down
4 changes: 4 additions & 0 deletions src/signal/impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,10 @@ impl SignalManager for PresageManager {
Ok(self.manager.clone().sync_contacts().await?)
}

fn profile_key(&self, id: Uuid) -> Option<ProfileKey> {
self.manager.store().profile_key(&id).ok().flatten()
}

fn profile_name(&self, id: Uuid) -> Option<String> {
let profile_key = self.manager.store().profile_key(&id).ok()??;
let profile = self.manager.store().profile(id, profile_key).ok()??;
Expand Down
4 changes: 3 additions & 1 deletion src/signal/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use std::pin::Pin;
use async_trait::async_trait;
use presage::libsignal_service::content::Content;
use presage::libsignal_service::models::Contact;
use presage::libsignal_service::prelude::Group;
use presage::libsignal_service::prelude::{Group, ProfileKey};
use presage::libsignal_service::sender::AttachmentSpec;
use presage::proto::AttachmentPointer;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -49,6 +49,8 @@ pub trait SignalManager {

fn send_reaction(&self, channel: &Channel, message: &Message, emoji: String, remove: bool);

fn profile_key(&self, id: Uuid) -> Option<ProfileKey>;

fn profile_name(&self, id: Uuid) -> Option<String>;

/// Resolves contact name from user's profile via Signal server
Expand Down
6 changes: 5 additions & 1 deletion src/signal/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::{cell::RefCell, rc::Rc};
use async_trait::async_trait;
use presage::libsignal_service::content::Content;
use presage::libsignal_service::models::Contact;
use presage::libsignal_service::prelude::{AttachmentIdentifier, Group};
use presage::libsignal_service::prelude::{AttachmentIdentifier, Group, ProfileKey};
use presage::libsignal_service::sender::AttachmentSpec;
use presage::proto::data_message::Quote;
use presage::proto::AttachmentPointer;
Expand Down Expand Up @@ -129,6 +129,10 @@ impl SignalManager for SignalManagerMock {
Ok(())
}

fn profile_key(&self, _id: Uuid) -> Option<ProfileKey> {
None
}

fn profile_name(&self, _id: Uuid) -> Option<String> {
None
}
Expand Down

0 comments on commit 021e89c

Please sign in to comment.