email/folder/
imap.rs

1use imap_client::imap_next::imap_types::{
2    core::{Atom, QuotedChar},
3    flag::FlagNameAttribute,
4    mailbox::Mailbox,
5};
6use tracing::debug;
7use utf7_imap::decode_utf7_imap as decode_utf7;
8
9use super::{Error, FolderKind, Result};
10use crate::{
11    account::config::AccountConfig,
12    folder::{Folder, Folders},
13};
14
15pub type ImapMailboxes = Vec<ImapMailbox>;
16
17impl Folders {
18    pub fn from_imap_mailboxes(config: &AccountConfig, mboxes: ImapMailboxes) -> Self {
19        mboxes
20            .into_iter()
21            .filter_map(|mbox| match Folder::try_from_imap_mailbox(config, &mbox) {
22                Ok(folder) => Some(folder),
23                Err(_err) => {
24                    debug!("skipping IMAP mailbox {:?}: {_err}", mbox.0.clone());
25                    None
26                }
27            })
28            .collect()
29    }
30}
31
32pub type ImapMailbox = (
33    Mailbox<'static>,
34    Option<QuotedChar>,
35    Vec<FlagNameAttribute<'static>>,
36);
37
38impl Folder {
39    fn try_from_imap_mailbox(
40        config: &AccountConfig,
41        (mbox, _delim, attrs): &ImapMailbox,
42    ) -> Result<Self> {
43        let mbox = match mbox {
44            Mailbox::Inbox => String::from("INBOX"),
45            Mailbox::Other(mbox) => String::from_utf8_lossy(mbox.as_ref()).to_string(),
46        };
47
48        // exit straight if the mailbox is not selectable.
49        // TODO: make this behaviour customizable?
50        if attrs.contains(&FlagNameAttribute::Noselect) {
51            return Err(Error::ParseImapFolderNotSelectableError(mbox.clone()));
52        }
53
54        let name = decode_utf7(mbox.into());
55
56        let kind = config
57            .find_folder_kind_from_alias(&name)
58            .or_else(|| find_folder_kind_from_imap_attrs(attrs.as_ref()))
59            .or_else(|| name.parse().ok());
60
61        let desc = attrs.iter().fold(String::default(), |mut desc, attr| {
62            if !desc.is_empty() {
63                desc.push_str(", ")
64            }
65            desc.push_str(&format!("{attr}"));
66            desc
67        });
68
69        Ok(Folder { kind, name, desc })
70    }
71}
72
73pub fn find_folder_kind_from_imap_attrs(attrs: &[FlagNameAttribute]) -> Option<FolderKind> {
74    attrs.iter().find_map(|attr| {
75        if attr == &FlagNameAttribute::from(Atom::try_from("Sent").unwrap()) {
76            Some(FolderKind::Sent)
77        } else if attr == &FlagNameAttribute::from(Atom::try_from("Drafts").unwrap()) {
78            Some(FolderKind::Drafts)
79        } else if attr == &FlagNameAttribute::from(Atom::try_from("Trash").unwrap()) {
80            Some(FolderKind::Trash)
81        } else {
82            None
83        }
84    })
85}