1use std::{collections::HashMap, fmt::Display, sync::Arc};
2
3use crate::{
4 error::{Error, ErrorKind},
5 runtime::thread::RwLock,
6 tree::Node,
7};
8
9#[cfg(feature = "imap")]
10use self::incoming::imap;
11
12#[cfg(feature = "pop")]
13use self::incoming::pop;
14
15#[cfg(feature = "maildir")]
16use self::incoming::maildir;
17
18#[cfg(all(feature = "smtp", feature = "runtime-tokio"))]
19use self::outgoing::smtp;
20
21use self::{
22 incoming::types::{
23 mailbox::Mailbox,
24 message::{Message, Preview},
25 },
26 outgoing::types::sendable::SendableMessage,
27 protocol::{IncomingProtocol, OutgoingProtocol},
28};
29
30pub use self::{
31 keep_alive::KeepAlive,
32 protocol::{Credentials, IncomingEmailProtocol, OutgoingEmailProtocol, ServerCredentials},
33};
34
35use crate::error::Result;
36
37mod incoming;
38mod outgoing;
39
40pub use incoming::types::*;
41pub use outgoing::types::*;
42
43pub mod address;
44pub mod builder;
45pub mod connection;
46pub mod content;
47
48mod parser;
49
50mod protocol;
51
52mod keep_alive;
53
54pub type Headers = HashMap<String, String>;
55
56pub struct EmailClient {
57 incoming: Box<dyn IncomingProtocol + Sync + Send>,
58 outgoing: Box<dyn OutgoingProtocol + Sync + Send>,
59}
60
61impl EmailClient {
62 pub fn new(
63 incoming: Box<dyn IncomingProtocol + Sync + Send>,
64 outgoing: Box<dyn OutgoingProtocol + Sync + Send>,
65 ) -> Self {
66 Self { incoming, outgoing }
67 }
68
69 pub async fn send_keep_alive(&mut self) -> Result<()> {
70 self.incoming.send_keep_alive().await
71 }
72
73 pub fn should_keep_alive(&self) -> bool {
74 self.incoming.should_keep_alive()
75 }
76
77 pub async fn get_mailbox_list(&mut self) -> Result<Node<Mailbox>> {
78 self.incoming.get_mailbox_list().await
79 }
80
81 pub async fn get_mailbox<BoxId: AsRef<str>>(
82 &mut self,
83 mailbox_id: BoxId,
84 ) -> Result<Node<Mailbox>> {
85 self.incoming.get_mailbox(mailbox_id.as_ref()).await
86 }
87
88 pub async fn rename_mailbox<OldName: AsRef<str>, NewName: AsRef<str>>(
89 &mut self,
90 old_name: OldName,
91 new_name: NewName,
92 ) -> Result<()> {
93 self.incoming
94 .rename_mailbox(old_name.as_ref(), new_name.as_ref())
95 .await
96 }
97
98 pub async fn delete_mailbox<BoxId: AsRef<str>>(&mut self, box_id: BoxId) -> Result<()> {
99 self.incoming.delete_mailbox(box_id.as_ref()).await
100 }
101
102 pub async fn create_mailbox<BoxName: AsRef<str>>(&mut self, box_id: BoxName) -> Result<()> {
103 self.incoming.create_mailbox(box_id.as_ref()).await
104 }
105
106 pub async fn get_messages<BoxId: AsRef<str>, S: Into<usize>, E: Into<usize>>(
107 &mut self,
108 box_id: BoxId,
109 start: S,
110 end: E,
111 ) -> Result<Vec<Preview>> {
112 let start = start.into();
113 let end = end.into();
114
115 if start >= end {
116 return Ok(Vec::new());
117 }
118
119 self.incoming
120 .get_messages(box_id.as_ref(), start, end)
121 .await
122 }
123
124 pub async fn get_message<BoxId: AsRef<str>, MessageId: AsRef<str>>(
125 &mut self,
126 box_id: BoxId,
127 message_id: MessageId,
128 ) -> Result<Message> {
129 self.incoming
130 .get_message(box_id.as_ref(), message_id.as_ref())
131 .await
132 }
133
134 pub async fn send_message<M: TryInto<SendableMessage, Error = impl Display>>(
135 &mut self,
136 message: M,
137 ) -> Result<()> {
138 let sendable = message.try_into().map_err(|err| {
139 Error::new(
140 ErrorKind::InvalidMessage,
141 format!("Failed to create sendable message: {}", err),
142 )
143 })?;
144
145 self.outgoing.send_message(sendable).await
146 }
147
148 pub async fn logout(&mut self) -> Result<()> {
149 self.incoming.logout().await
150 }
151}
152
153pub async fn create(
154 incoming: IncomingEmailProtocol,
155 outgoing: OutgoingEmailProtocol,
156) -> Result<EmailClient> {
157 let incoming_protocol = match incoming {
158 #[cfg(feature = "imap")]
159 IncomingEmailProtocol::Imap(credentials) => {
160 imap::create(&credentials, Default::default()).await?
161 }
162
163 #[cfg(feature = "pop")]
164 IncomingEmailProtocol::Pop(credentials) => pop::create(&credentials).await?,
165
166 #[cfg(feature = "maildir")]
167 IncomingEmailProtocol::Maildir(path) => maildir::create(path)?,
168
169 #[cfg(not(any(feature = "imap", feature = "pop")))]
170 _ => {
171 use crate::error::{err, ErrorKind};
172
173 err!(
174 ErrorKind::NoClientAvailable,
175 "There are no incoming mail clients supported",
176 );
177 }
178 };
179
180 let outgoing_protocol = match outgoing {
181 #[cfg(all(feature = "smtp", feature = "runtime-tokio"))]
182 OutgoingEmailProtocol::Smtp(credentials) => smtp::create(credentials)?,
183 #[cfg(not(any(all(feature = "smtp", feature = "runtime-tokio"))))]
184 _ => {
185 use crate::error::{err, ErrorKind};
186
187 err!(
188 ErrorKind::NoClientAvailable,
189 "There are no outgoing mail clients supported",
190 );
191 }
192 };
193
194 let client = EmailClient::new(incoming_protocol, outgoing_protocol);
195
196 Ok(client)
197}
198
199pub struct ThreadableEmailClient {
201 client: Arc<RwLock<EmailClient>>,
202 keep_alive: KeepAlive,
203}
204
205impl AsRef<Arc<RwLock<EmailClient>>> for ThreadableEmailClient {
206 fn as_ref(&self) -> &Arc<RwLock<EmailClient>> {
207 &self.client
208 }
209}
210
211impl ThreadableEmailClient {
212 pub fn new(client: Arc<RwLock<EmailClient>>, mut keep_alive: KeepAlive) -> Self {
213 keep_alive.start();
214
215 Self { client, keep_alive }
216 }
217
218 pub fn keep_alive(&self) -> &KeepAlive {
219 &self.keep_alive
220 }
221}
222
223impl From<EmailClient> for ThreadableEmailClient {
224 fn from(client: EmailClient) -> Self {
225 let client = Arc::new(RwLock::new(client));
226
227 let keep_alive: KeepAlive = Arc::clone(&client).into();
228
229 Self::new(client, keep_alive)
230 }
231}