imsg_session/watch/
mod.rs1use map_core::client::MapClient;
4use map_core::folders::Folder;
5use map_core::MessageStatus;
6use store::{Direction, NewMessage, OutgoingStatus, PhoneField, Store};
7use tokio::io::{AsyncRead, AsyncWrite};
8use tokio::sync::{mpsc, watch};
9
10use crate::outbox::drain_outbox;
11use crate::sync::backfill_catch_up;
12use crate::util::now_ms;
13use crate::{EventType, MnsEvent};
14
15fn parse_folder(s: &str) -> Option<Folder> {
18 let leaf = s.rsplit('/').next().unwrap_or(s);
20 match leaf.to_ascii_lowercase().as_str() {
21 "inbox" => Some(Folder::Inbox),
22 "sent" => Some(Folder::Sent),
23 "outbox" => Some(Folder::Outbox),
24 "deleted" => Some(Folder::Deleted),
25 _ => None,
26 }
27}
28
29pub async fn handle_mns_event<T: AsyncRead + AsyncWrite + Unpin>(
46 event: &MnsEvent,
47 client: &mut MapClient<T>,
48 store: &Store,
49 now: i64,
50) -> anyhow::Result<()> {
51 match event.event_type() {
52 EventType::NewMessage => handle_new_message(event, client, store, now).await?,
53 EventType::MessageDeleted => {
54 if let Some(handle) = event.handle() {
55 store.delete_by_handle(handle).await?;
56 }
57 }
58 EventType::MessageShift => {
59 if let (Some(handle), Some(folder)) = (event.handle(), event.folder()) {
60 store.update_folder(handle, folder).await?;
61 }
62 }
63 EventType::ReadStatusChanged => handle_read_status_changed(event, client, store).await?,
64 EventType::DeliverySuccess | EventType::SendingSuccess => {
65 mark_outgoing(event, store, OutgoingStatus::SentConfirmed).await?;
66 }
67 EventType::DeliveryFailure | EventType::SendingFailure => {
68 mark_outgoing(event, store, OutgoingStatus::FailedPermanent).await?;
69 }
70 EventType::MemoryFull => {
71 tracing::warn!(
72 "device message store is full — new messages may be rejected until freed"
73 );
74 }
75 EventType::MemoryAvailable => {
76 tracing::info!("device message store has space available again");
77 }
78 }
79 Ok(())
80}
81
82async fn handle_new_message<T: AsyncRead + AsyncWrite + Unpin>(
85 event: &MnsEvent,
86 client: &mut MapClient<T>,
87 store: &Store,
88 now: i64,
89) -> anyhow::Result<()> {
90 let (Some(handle), Some(folder_raw)) = (event.handle(), event.folder()) else {
91 tracing::warn!("NewMessage event missing handle or folder — skipped");
92 return Ok(());
93 };
94 let Some(folder) = parse_folder(folder_raw) else {
95 tracing::warn!("NewMessage event unknown folder {folder_raw} — skipped");
96 return Ok(());
97 };
98 client.set_folder(folder).await?;
99 let bmsg = client.get_message(handle).await?;
100 let address = bmsg.originator().map(|o| o.tel.clone()).unwrap_or_default();
101 let status = i32::from(matches!(bmsg.status(), MessageStatus::Read));
102 let msg = NewMessage {
103 map_handle: handle.to_owned(),
104 timestamp_ms: now,
105 folder: folder_raw.to_owned(),
106 direction: Direction::Received,
107 address: PhoneField::new(&address, None),
108 status,
109 synced_at: now,
110 text: bmsg.envelope().body.text.clone(),
111 outgoing_status: None,
112 };
113 store.upsert(msg).await?;
114 Ok(())
115}
116
117async fn handle_read_status_changed<T: AsyncRead + AsyncWrite + Unpin>(
123 event: &MnsEvent,
124 client: &mut MapClient<T>,
125 store: &Store,
126) -> anyhow::Result<()> {
127 let (Some(handle), Some(folder_raw)) = (event.handle(), event.folder()) else {
128 tracing::warn!("ReadStatusChanged event missing handle or folder — skipped");
129 return Ok(());
130 };
131 let Some(folder) = parse_folder(folder_raw) else {
132 tracing::warn!("ReadStatusChanged event unknown folder {folder_raw} — skipped");
133 return Ok(());
134 };
135 client.set_folder(folder).await?;
136 let bmsg = client.get_message(handle).await?;
137 let status = i32::from(matches!(bmsg.status(), MessageStatus::Read));
138 store.update_status(handle, status).await?;
139 Ok(())
140}
141
142async fn mark_outgoing(
144 event: &MnsEvent,
145 store: &Store,
146 status: OutgoingStatus,
147) -> anyhow::Result<()> {
148 if let Some(handle) = event.handle() {
149 store.update_outgoing_status(handle, status).await?;
150 }
151 Ok(())
152}
153
154pub async fn run_watch<T: AsyncRead + AsyncWrite + Unpin>(
171 event_rx: &mut mpsc::Receiver<MnsEvent>,
172 client: &mut MapClient<T>,
173 store: &Store,
174 mut cancel_rx: watch::Receiver<bool>,
175) -> anyhow::Result<()> {
176 backfill_catch_up(client, store).await?;
177 drain_outbox(client, store, now_ms()).await?;
178 loop {
179 tokio::select! {
180 biased;
181 _ = cancel_rx.changed() => {
182 if *cancel_rx.borrow() { break; }
183 }
184 event = event_rx.recv() => {
185 let Some(ev) = event else { break; };
186 handle_mns_event(&ev, client, store, now_ms()).await?;
187 }
188 }
189 }
190 Ok(())
191}
192
193#[cfg(test)]
194mod tests;