imsg_session/contacts/live.rs
1//! Live PBAP operations — `list`/`get`/`pull_all`/`lookup` read straight from the device, no
2//! store writes. Mirrors `session::live`'s relationship to `map_core`: both the CLI hub path
3//! and the broker's dispatch call these instead of `PbapClient` methods directly.
4
5use pbap_core::client::PbapClient;
6use pbap_core::phonebook::{PhonebookPath, SearchAttribute};
7use pbap_core::{CardEntry, Contact};
8use tokio::io::{AsyncRead, AsyncWrite};
9
10/// Lists contact identities in `path`, windowed by `limit`/`offset`. No vCard bodies fetched.
11///
12/// # Errors
13///
14/// Returns an error if the PBAP listing fails.
15pub async fn list<T: AsyncRead + AsyncWrite + Unpin>(
16 client: &mut PbapClient<T>,
17 path: PhonebookPath,
18 limit: Option<u16>,
19 offset: u16,
20) -> anyhow::Result<Vec<CardEntry>> {
21 Ok(client.list(path, limit, offset).await?)
22}
23
24/// Fetches one contact's full vCard by its current PBAP handle.
25///
26/// `handle` is volatile — re-resolved on every [`list`] call — so callers must not cache it
27/// across syncs; see `formats::vcard::Contact::uid` for the durable alternative.
28///
29/// # Errors
30///
31/// Returns an error if the PBAP pull fails.
32pub async fn get<T: AsyncRead + AsyncWrite + Unpin>(
33 client: &mut PbapClient<T>,
34 path: PhonebookPath,
35 handle: &str,
36) -> anyhow::Result<Contact> {
37 Ok(client.pull(path, handle).await?)
38}
39
40/// Fetches every contact in `path`, windowed by `limit`/`offset`.
41///
42/// # Errors
43///
44/// Returns an error if the PBAP pull fails.
45pub async fn pull_all<T: AsyncRead + AsyncWrite + Unpin>(
46 client: &mut PbapClient<T>,
47 path: PhonebookPath,
48 limit: Option<u16>,
49 offset: u16,
50) -> anyhow::Result<Vec<Contact>> {
51 Ok(client.pull_all(path, limit, offset).await?)
52}
53
54/// Reverse-looks-up a contact by phone number via device-side `SearchAttribute::Number`, then
55/// pulls its full vCard. Returns `None` if the device reports no match.
56///
57/// Matching is whatever the device's own search implements — unlike the removed
58/// `PbapClient::find_by_number`, no client-side E.164 normalization is applied here.
59///
60/// # Errors
61///
62/// Returns an error if the PBAP search or pull fails.
63pub async fn lookup<T: AsyncRead + AsyncWrite + Unpin>(
64 client: &mut PbapClient<T>,
65 path: PhonebookPath,
66 number: &str,
67) -> anyhow::Result<Option<Contact>> {
68 let entries = client.search(path, SearchAttribute::Number, number, None, 0).await?;
69 let Some(entry) = entries.iter().find(|e| e.handle() != "0.vcf") else {
70 return Ok(None);
71 };
72 Ok(Some(client.pull(path, entry.handle()).await?))
73}
74
75#[cfg(test)]
76mod tests;