dust-mail 0.4.0

A multi protocol email client
use async_trait::async_trait;

#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

use crate::{error::Result, tree::Node};

use super::{
    connection::ConnectionSecurity,
    incoming::types::{
        mailbox::Mailbox,
        message::{Message, Preview},
    },
    outgoing::types::message::SendableMessage,
};

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct RemoteServer {
    server: String,
    port: u16,
    security: ConnectionSecurity,
}

impl RemoteServer {
    pub fn new<Server: Into<String>>(
        server: Server,
        port: u16,
        security: ConnectionSecurity,
    ) -> Self {
        Self {
            server: server.into(),
            port,
            security,
        }
    }

    pub fn security(&self) -> &ConnectionSecurity {
        &self.security
    }

    pub fn domain(&self) -> &str {
        self.server.as_ref()
    }

    pub fn port(&self) -> u16 {
        self.port
    }
}

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Credentials {
    Password { username: String, password: String },
    OAuth { username: String, token: String },
}

impl Credentials {
    pub fn username(&self) -> &str {
        match &self {
            Credentials::OAuth { username, .. } => username,
            Credentials::Password { username, .. } => username,
        }
    }
}

impl Credentials {
    pub fn password<U: Into<String>, P: Into<String>>(username: U, password: P) -> Self {
        Credentials::Password {
            username: username.into(),
            password: password.into(),
        }
    }

    pub fn oauth<U: Into<String>, T: Into<String>>(username: U, token: T) -> Self {
        Credentials::OAuth {
            username: username.into(),
            token: token.into(),
        }
    }
}

pub trait ServerCredentials {
    fn credentials(&self) -> &Credentials;
}

#[cfg(feature = "smtp")]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct SmtpCredentials {
    server: RemoteServer,
    credentials: Credentials,
}

#[cfg(feature = "smtp")]
impl SmtpCredentials {
    pub fn new(server: RemoteServer, credentials: Credentials) -> Self {
        Self {
            server,
            credentials,
        }
    }

    pub fn server(&self) -> &RemoteServer {
        &self.server
    }
}

#[cfg(feature = "smtp")]
impl ServerCredentials for SmtpCredentials {
    fn credentials(&self) -> &Credentials {
        &self.credentials
    }
}

#[cfg(feature = "imap")]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ImapCredentials {
    server: RemoteServer,
    credentials: Credentials,
}

#[cfg(feature = "imap")]
impl ImapCredentials {
    pub fn new(server: RemoteServer, credentials: Credentials) -> Self {
        Self {
            server,
            credentials,
        }
    }

    pub fn server(&self) -> &RemoteServer {
        &self.server
    }
}

#[cfg(feature = "imap")]
impl ServerCredentials for ImapCredentials {
    fn credentials(&self) -> &Credentials {
        &self.credentials
    }
}

#[cfg(feature = "pop")]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PopCredentials {
    server: RemoteServer,
    credentials: Credentials,
}

#[cfg(feature = "pop")]
impl PopCredentials {
    pub fn new(server: RemoteServer, credentials: Credentials) -> Self {
        Self {
            server,
            credentials,
        }
    }

    pub fn server(&self) -> &RemoteServer {
        &self.server
    }
}

#[cfg(feature = "pop")]
impl ServerCredentials for PopCredentials {
    fn credentials(&self) -> &Credentials {
        &self.credentials
    }
}

#[async_trait]
pub trait IncomingProtocol {
    async fn send_keep_alive(&mut self) -> Result<()>;

    fn should_keep_alive(&self) -> bool;

    async fn get_mailbox_list(&mut self) -> Result<Node<Mailbox>>;

    async fn get_mailbox(&mut self, mailbox_id: &str) -> Result<Node<Mailbox>>;

    async fn rename_mailbox(&mut self, old_name: &str, new_name: &str) -> Result<()>;

    async fn create_mailbox(&mut self, name: &str) -> Result<()>;

    async fn delete_mailbox(&mut self, box_id: &str) -> Result<()>;

    async fn get_messages(
        &mut self,
        box_id: &str,
        start: usize,
        end: usize,
    ) -> Result<Vec<Preview>>;

    async fn get_message(&mut self, box_id: &str, message_id: &str) -> Result<Message>;

    async fn logout(&mut self) -> Result<()>;
}

#[async_trait]
pub trait OutgoingProtocol {
    async fn send_message(&mut self, message: SendableMessage) -> Result<()>;
}

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum IncomingEmailProtocol {
    #[cfg(feature = "imap")]
    Imap(ImapCredentials),

    #[cfg(feature = "pop")]
    Pop(PopCredentials),
}

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum OutgoingEmailProtocol {
    #[cfg(feature = "smtp")]
    Smtp(SmtpCredentials),
}

pub struct IncomingConfig {
    #[cfg(feature = "maildir")]
    pub(crate) maildir: Option<String>,
}

impl Default for IncomingConfig {
    fn default() -> Self {
        let mut config = Self::new();

        #[cfg(feature = "maildir")]
        {
            use directories::BaseDirs;

            if let Some(dirs) = BaseDirs::new() {
                config = config.maildir(dirs.cache_dir().join("mail").display());
            }
        }

        config
    }
}

impl IncomingConfig {
    pub fn new() -> Self {
        Self {
            #[cfg(feature = "maildir")]
            maildir: None,
        }
    }

    #[cfg(feature = "maildir")]
    pub fn maildir<P: std::fmt::Display>(mut self, path: P) -> Self {
        self.maildir = Some(path.to_string());

        self
    }
}