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
19const PAGE_ENRICHMENT_BATCH: usize = 250;
21
22#[cfg(test)]
23thread_local! {
24 static BEFORE_ENRICHMENT: std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
25}
26
27fn with_page_snapshot<T>(
28 conn: &impl DbQuery,
29 read: impl FnOnce(&mut SqliteConnection) -> Result<T, EnrichMessageError>,
30) -> Result<T, EnrichMessageError> {
31 conn.raw_query(|sqlite| Ok(sqlite.transaction(read)))?
33}
34
35fn visible_recovery_query(mut query: RecoveryQueryArgs) -> RecoveryQueryArgs {
36 query.messages = filter_out_hidden_message_types_from_query(&query.messages);
37 query
38}
39
40fn enrich_page_rows(
41 conn: &impl DbQuery,
42 rows: Vec<AppVisibleMessageRow>,
43) -> Result<Vec<EnrichedStoredMessage>, EnrichMessageError> {
44 #[cfg(test)]
45 BEFORE_ENRICHMENT.with_borrow_mut(|hook| {
46 if let Some(hook) = hook.take() {
47 hook();
48 }
49 });
50 let positions: std::collections::HashMap<_, _> = rows
51 .iter()
52 .enumerate()
53 .map(|(index, row)| (row.stored.id.clone(), (index, row.cursor)))
54 .collect();
55 let mut sources: std::collections::HashMap<_, Vec<_>> = std::collections::HashMap::new();
56 for row in rows {
57 sources
58 .entry(row.stored.group_id)
59 .or_default()
60 .push(row.stored);
61 }
62 let mut messages = Vec::with_capacity(positions.len());
63 for (group_id, rows) in sources {
64 let mut rows = rows.into_iter();
65 while rows.len() > PAGE_ENRICHMENT_BATCH {
66 let batch = rows.by_ref().take(PAGE_ENRICHMENT_BATCH).collect();
67 messages.extend(enrich_messages_with_stored(conn, &group_id, batch)?);
68 }
69 messages.extend(enrich_messages_with_stored(
70 conn,
71 &group_id,
72 rows.collect(),
73 )?);
74 }
75 messages.sort_by_key(|message| positions[&message.stored.id].0);
77 for message in &mut messages {
78 message.delivery_cursor = positions[&message.stored.id].1;
79 }
80 Ok(messages)
81}
82
83fn history_page_with_stored(
84 conn: &impl DbQuery,
85 group_id: &xmtp_proto::types::GroupId,
86 query: &MsgQueryArgs,
87) -> Result<EnrichedHistoryPage, EnrichMessageError> {
88 with_page_snapshot(conn, |sqlite| {
89 let store = sqlite.key_store();
90 let conn = store.db();
91 let page =
92 conn.history_page_rows(group_id, &filter_out_hidden_message_types_from_query(query))?;
93 Ok(EnrichedHistoryPage {
94 messages: enrich_page_rows(&conn, page.rows)?,
95 first_position: page.first_position,
96 last_position: page.last_position,
97 has_more: page.has_more,
98 })
99 })
100}
101
102fn recovery_page_with_stored(
103 conn: &impl DbQuery,
104 group_id: &xmtp_proto::types::GroupId,
105 query: RecoveryQueryArgs,
106) -> Result<EnrichedRecoveryPage, EnrichMessageError> {
107 let query = visible_recovery_query(query);
108 with_page_snapshot(conn, |sqlite| {
109 let store = sqlite.key_store();
110 let conn = store.db();
111 let page = conn.recovery_page_rows(group_id, &query)?;
112 Ok(EnrichedRecoveryPage {
113 messages: enrich_page_rows(&conn, page.rows)?,
114 first_position: page.first_position,
115 last_position: page.last_position,
116 has_more: page.has_more,
117 })
118 })
119}
120
121impl<Context: XmtpSharedContext> MlsGroup<Context> {
122 pub fn find_history_page_with_stored(
124 &self,
125 query: &MsgQueryArgs,
126 ) -> Result<EnrichedHistoryPage, EnrichMessageError> {
127 history_page_with_stored(&self.context.db(), &self.group_id, query)
128 }
129
130 pub fn find_recovery_page_with_stored(
133 &self,
134 query: RecoveryQueryArgs,
135 ) -> Result<EnrichedRecoveryPage, EnrichMessageError> {
136 recovery_page_with_stored(&self.context.db(), &self.group_id, query)
137 }
138}
139
140#[cfg(test)]
141mod tests;