Skip to main content

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}