xmtp_mls/groups/message_list/
page.rs1use super::*;
2use xmtp_db::{
3 TransactionalKeyStore, XmtpMlsStorageProvider,
4 delivery::{AppVisibleMessageRow, QueryDelivery},
5 diesel::{Connection, SqliteConnection},
6 group_message::RecoveryQueryArgs,
7};
8
9pub struct EnrichedMessagePage<P> {
10 pub messages: Vec<EnrichedStoredMessage>,
11 pub first_position: Option<P>,
12 pub last_position: Option<P>,
13 pub has_more: bool,
14}
15
16pub type EnrichedHistoryPage = EnrichedMessagePage<xmtp_db::delivery::HistoryPosition>;
17pub type EnrichedRecoveryPage = EnrichedMessagePage<xmtp_db::group_message::RecoveryPosition>;
18
19#[cfg(test)]
20thread_local! {
21 static BEFORE_ENRICHMENT: std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
22}
23
24fn with_page_snapshot<T>(
25 conn: &impl DbQuery,
26 read: impl FnOnce(&mut SqliteConnection) -> Result<T, EnrichMessageError>,
27) -> Result<T, EnrichMessageError> {
28 conn.raw_query(|sqlite| Ok(sqlite.transaction(read)))?
30}
31
32fn visible_recovery_query(mut query: RecoveryQueryArgs) -> RecoveryQueryArgs {
33 query.messages = filter_out_hidden_message_types_from_query(&query.messages);
34 query
35}
36
37fn enrich_page_rows(
38 conn: &impl DbQuery,
39 rows: Vec<AppVisibleMessageRow>,
40) -> Result<Vec<EnrichedStoredMessage>, EnrichMessageError> {
41 #[cfg(test)]
42 BEFORE_ENRICHMENT.with_borrow_mut(|hook| {
43 if let Some(hook) = hook.take() {
44 hook();
45 }
46 });
47 let positions: std::collections::HashMap<_, _> = rows
48 .iter()
49 .enumerate()
50 .map(|(index, row)| (row.stored.id.clone(), (index, row.cursor)))
51 .collect();
52 let mut sources: std::collections::HashMap<_, Vec<_>> = std::collections::HashMap::new();
53 for row in rows {
54 sources
55 .entry(row.stored.group_id)
56 .or_default()
57 .push(row.stored);
58 }
59 let mut messages = Vec::with_capacity(positions.len());
60 for (group_id, rows) in sources {
61 messages.extend(enrich_messages_with_stored(conn, &group_id, rows)?);
62 }
63 messages.sort_by_key(|message| positions[&message.stored.id].0);
65 for message in &mut messages {
66 message.delivery_cursor = positions[&message.stored.id].1;
67 }
68 Ok(messages)
69}
70
71fn history_page_with_stored(
72 conn: &impl DbQuery,
73 group_id: &xmtp_proto::types::GroupId,
74 query: &MsgQueryArgs,
75) -> Result<EnrichedHistoryPage, EnrichMessageError> {
76 with_page_snapshot(conn, |sqlite| {
77 let store = sqlite.key_store();
78 let conn = store.db();
79 let page =
80 conn.history_page_rows(group_id, &filter_out_hidden_message_types_from_query(query))?;
81 Ok(EnrichedHistoryPage {
82 messages: enrich_page_rows(&conn, page.rows)?,
83 first_position: page.first_position,
84 last_position: page.last_position,
85 has_more: page.has_more,
86 })
87 })
88}
89
90fn recovery_page_with_stored(
91 conn: &impl DbQuery,
92 group_id: &xmtp_proto::types::GroupId,
93 query: RecoveryQueryArgs,
94) -> Result<EnrichedRecoveryPage, EnrichMessageError> {
95 let query = visible_recovery_query(query);
96 with_page_snapshot(conn, |sqlite| {
97 let store = sqlite.key_store();
98 let conn = store.db();
99 let page = conn.recovery_page_rows(group_id, &query)?;
100 Ok(EnrichedRecoveryPage {
101 messages: enrich_page_rows(&conn, page.rows)?,
102 first_position: page.first_position,
103 last_position: page.last_position,
104 has_more: page.has_more,
105 })
106 })
107}
108
109impl<Context: XmtpSharedContext> MlsGroup<Context> {
110 pub fn find_history_page_with_stored(
112 &self,
113 query: &MsgQueryArgs,
114 ) -> Result<EnrichedHistoryPage, EnrichMessageError> {
115 history_page_with_stored(&self.context.db(), &self.group_id, query)
116 }
117
118 pub fn find_recovery_page_with_stored(
121 &self,
122 query: RecoveryQueryArgs,
123 ) -> Result<EnrichedRecoveryPage, EnrichMessageError> {
124 recovery_page_with_stored(&self.context.db(), &self.group_id, query)
125 }
126}
127
128#[cfg(test)]
129mod tests;