imsg_map/client/nav.rs
1//! SETPATH sequencing: folder navigation and the depth bookkeeping it depends on.
2
3use futures::SinkExt;
4use obex_core::client::ObexClient;
5use tokio::io::{AsyncRead, AsyncWrite};
6
7use super::MapClient;
8use crate::{folders::Folder, MapError};
9
10impl<T: AsyncRead + AsyncWrite + Unpin> MapClient<T> {
11 /// `segment` is a single path component, not a slash-joined path — iOS requires one
12 /// SETPATH per level. Does not validate that `segment` names an existing folder.
13 ///
14 /// # Errors
15 ///
16 /// Returns [`MapError`] if the request fails to encode, the transport closes, or the
17 /// server returns a non-OK response.
18 pub(crate) async fn setpath(&mut self, segment: &str) -> Result<(), MapError> {
19 let req = self.obex.setpath_request(segment)?;
20 self.transport.send(req).await?;
21 let rsp_bytes = Self::recv(&mut self.transport).await?;
22 let rsp = ObexClient::parse_response(&rsp_bytes)?;
23 if !rsp.opcode.is_ok() {
24 return Err(MapError::ServerError(rsp.opcode.to_byte()));
25 }
26 self.depth = self.depth.saturating_add(1);
27 Ok(())
28 }
29
30 /// Decrements `depth` on success. Does not check whether depth is already zero.
31 ///
32 /// # Errors
33 ///
34 /// Returns [`MapError`] if the request fails to encode, the transport closes, or the
35 /// server returns a non-OK response.
36 pub(crate) async fn setpath_up(&mut self) -> Result<(), MapError> {
37 debug_assert!(self.depth > 0, "setpath_up called at root");
38 let req = self.obex.setpath_backup_request()?;
39 self.transport.send(req).await?;
40 let rsp_bytes = Self::recv(&mut self.transport).await?;
41 let rsp = ObexClient::parse_response(&rsp_bytes)?;
42 if !rsp.opcode.is_ok() {
43 return Err(MapError::ServerError(rsp.opcode.to_byte()));
44 }
45 self.depth = self.depth.saturating_sub(1);
46 Ok(())
47 }
48
49 /// No-op when already at root (`depth == 0`). Does not navigate anywhere after resetting.
50 ///
51 /// # Errors
52 ///
53 /// Returns [`MapError`] if any backup SETPATH fails to encode, the transport closes, or the
54 /// server returns a non-OK response.
55 pub(crate) async fn reset_to_root(&mut self) -> Result<(), MapError> {
56 for _ in 0..self.depth {
57 self.setpath_up().await?;
58 }
59 Ok(())
60 }
61
62 /// If already inside a subfolder (e.g. after a prior `set_folder` call), backs up to root
63 /// first, then navigates `telecom` → `msg` → folder. iOS requires one SETPATH per level;
64 /// a single slash-joined path is rejected.
65 ///
66 /// # Errors
67 ///
68 /// Returns [`MapError`] if any SETPATH fails to encode, the transport closes, or the
69 /// server returns a non-OK response for any step.
70 pub async fn set_folder(&mut self, folder: Folder) -> Result<(), MapError> {
71 self.reset_to_root().await?;
72 for segment in ["telecom", "msg", folder.as_str()] {
73 self.setpath(segment).await?;
74 }
75 Ok(())
76 }
77}