Skip to main content

io_webdav/rfc6352/addressbook/
list.rs

1//! `list-addressbooks` coroutine: PROPFIND Depth:1 against the
2//! addressbook home-set URL, collecting every child collection whose
3//! resourcetype is `<C:addressbook/>`.
4//!
5//! # Example
6//!
7//! ```rust,no_run
8//! use std::{
9//!     io::{Read, Write},
10//!     net::TcpStream,
11//! };
12//!
13//! use io_webdav::{
14//!     coroutine::{WebdavCoroutine, WebdavCoroutineState, WebdavYield},
15//!     rfc4918::WebdavAuth,
16//!     rfc6352::addressbook::list::ListAddressbooks,
17//! };
18//! use url::Url;
19//!
20//! // Ready stream needed (TCP-connected, TLS-negociated)
21//! let mut stream = TcpStream::connect("dav.example.org:443").unwrap();
22//! let mut buf = [0u8; 4096];
23//!
24//! let base_url: Url = "https://dav.example.org/".parse().unwrap();
25//! let auth = WebdavAuth::None;
26//! let mut coroutine =
27//!     ListAddressbooks::new(&base_url, &auth, "io-webdav", "/dav/addressbooks/");
28//! let mut arg = None;
29//!
30//! let addressbooks = loop {
31//!     match coroutine.resume(arg.take()) {
32//!         WebdavCoroutineState::Yielded(WebdavYield::WantsWrite(bytes)) => {
33//!             stream.write_all(&bytes).unwrap();
34//!         }
35//!         WebdavCoroutineState::Yielded(WebdavYield::WantsRead) => {
36//!             let n = stream.read(&mut buf).unwrap();
37//!             arg = Some(&buf[..n]);
38//!         }
39//!         WebdavCoroutineState::Complete(Ok(addressbooks)) => break addressbooks,
40//!         WebdavCoroutineState::Complete(Err(err)) => panic!("{err}"),
41//!     }
42//! };
43//!
44//! println!("{} addressbooks", addressbooks.len());
45//! ```
46
47use alloc::{collections::BTreeSet, string::ToString};
48
49use log::trace;
50use url::Url;
51
52use crate::{
53    coroutine::*,
54    rfc4918::{
55        DISPLAYNAME, GETCTAG, RESOURCETYPE, ResponseEntry, SYNC_TOKEN, WebdavAuth,
56        propfind::Propfind, send::SendError, trace_unrecognized,
57    },
58    rfc6352::addressbook::{
59        ADDRESSBOOK, ADDRESSBOOK_COLOR, ADDRESSBOOK_DESCRIPTION, Addressbook, LIST_PROPS,
60    },
61    webdav_try,
62};
63
64/// Coroutine that lists addressbooks under `home_set_path`.
65#[derive(Debug)]
66pub struct ListAddressbooks {
67    state: State,
68}
69
70impl ListAddressbooks {
71    /// Builds a new `list-addressbooks` coroutine.
72    pub fn new(base_url: &Url, auth: &WebdavAuth, user_agent: &str, home_set_path: &str) -> Self {
73        let propfind = Propfind::new(base_url, auth, user_agent, home_set_path, 1, LIST_PROPS);
74        Self {
75            state: State::Propfind(propfind),
76        }
77    }
78}
79
80impl WebdavCoroutine for ListAddressbooks {
81    type Yield = WebdavYield;
82    type Return = Result<BTreeSet<Addressbook>, SendError>;
83
84    fn resume(&mut self, arg: Option<&[u8]>) -> WebdavCoroutineState<Self::Yield, Self::Return> {
85        trace!("sending request");
86        match &mut self.state {
87            State::Propfind(propfind) => {
88                let multistatus = webdav_try!(propfind, arg);
89                let addressbooks = multistatus
90                    .responses
91                    .iter()
92                    .filter_map(from_entry)
93                    .collect();
94                WebdavCoroutineState::Complete(Ok(addressbooks))
95            }
96        }
97    }
98}
99
100fn from_entry(entry: &ResponseEntry) -> Option<Addressbook> {
101    if !entry.has_resource_type(RESOURCETYPE, ADDRESSBOOK) {
102        trace!("skip non-addressbook response {}", entry.href);
103        return None;
104    }
105
106    let id = entry.id();
107    if id.is_empty() {
108        return None;
109    }
110
111    trace_unrecognized(entry, LIST_PROPS);
112
113    Some(Addressbook {
114        id: id.to_string(),
115        display_name: entry.text(DISPLAYNAME).map(ToString::to_string),
116        description: entry.text(ADDRESSBOOK_DESCRIPTION).map(ToString::to_string),
117        color: entry.text(ADDRESSBOOK_COLOR).map(ToString::to_string),
118        ctag: entry.text(GETCTAG).map(ToString::to_string),
119        sync_token: entry.text(SYNC_TOKEN).map(ToString::to_string),
120    })
121}
122
123#[derive(Debug)]
124enum State {
125    Propfind(Propfind),
126}