use std::{
collections::BTreeSet,
io::{Read, Write},
};
use anyhow::{Result, bail};
#[cfg(feature = "dav")]
use crate::dav::client::DavClient;
#[cfg(feature = "imap")]
use crate::imap::client::ImapClient;
#[cfg(feature = "msgraph")]
use crate::msgraph::client::GraphClient;
use crate::{
account::{SourceAccount, SourceAccountBackend},
item::{collection::Collection, flag::Flag, flag::FlagOp, summary::ItemSummary},
kind::LinkId,
};
pub struct Enumeration {
pub items: Vec<EnumEntry>,
pub vanished: Vec<String>,
pub complete: bool,
pub checkpoint: Vec<u8>,
}
pub struct EnumEntry {
pub id: String,
pub flags: BTreeSet<Flag>,
pub revision: Option<String>,
}
pub struct WrittenItem {
pub id: String,
pub revision: Option<String>,
}
pub enum Client {
#[cfg(feature = "imap")]
Imap(ImapClient),
#[cfg(feature = "dav")]
Dav(Box<DavClient>),
#[cfg(feature = "msgraph")]
Msgraph(Box<GraphClient>),
#[cfg(not(any(feature = "imap", feature = "msgraph", feature = "dav")))]
#[allow(dead_code)]
Unavailable,
}
#[cfg(not(any(feature = "imap", feature = "msgraph", feature = "dav")))]
const NO_BACKEND: &str =
"No sync backend is compiled in (rebuild with the `imap`, `msgraph` or `dav` cargo feature)";
#[cfg_attr(
not(all(feature = "imap", feature = "msgraph", feature = "dav")),
allow(unused_variables)
)]
impl Client {
pub fn list_collections(&mut self, with_counts: bool) -> Result<Vec<Collection>> {
match self {
#[cfg(feature = "imap")]
Client::Imap(c) => c.list_mailboxes(with_counts),
#[cfg(feature = "msgraph")]
Client::Msgraph(c) => c.list_mailboxes(with_counts),
#[cfg(feature = "dav")]
Client::Dav(c) => c.list_collections(with_counts),
#[cfg(not(any(feature = "imap", feature = "msgraph", feature = "dav")))]
Client::Unavailable => bail!(NO_BACKEND),
}
}
pub fn create_collection(&mut self, collection: &str) -> Result<()> {
match self {
#[cfg(feature = "imap")]
Client::Imap(c) => c.create_mailbox(collection),
#[cfg(feature = "msgraph")]
Client::Msgraph(_) => bail!("Graph mailboxes are pull-only (create not supported)"),
#[cfg(feature = "dav")]
Client::Dav(c) => c.create_collection(collection),
#[cfg(not(any(feature = "imap", feature = "msgraph", feature = "dav")))]
Client::Unavailable => bail!(NO_BACKEND),
}
}
pub fn delete_collection(&mut self, collection: &str) -> Result<()> {
match self {
#[cfg(feature = "imap")]
Client::Imap(c) => c.delete_mailbox(collection),
#[cfg(feature = "msgraph")]
Client::Msgraph(_) => bail!("Graph mailboxes are pull-only (delete not supported)"),
#[cfg(feature = "dav")]
Client::Dav(c) => c.delete_collection(collection),
#[cfg(not(any(feature = "imap", feature = "msgraph", feature = "dav")))]
Client::Unavailable => bail!(NO_BACKEND),
}
}
pub fn enumerate(&mut self, collection: &str, cursor: Option<&[u8]>) -> Result<Enumeration> {
match self {
#[cfg(feature = "imap")]
Client::Imap(c) => c.enumerate(collection, cursor),
#[cfg(feature = "msgraph")]
Client::Msgraph(c) => c.enumerate(collection, cursor),
#[cfg(feature = "dav")]
Client::Dav(c) => c.enumerate(collection, cursor),
#[cfg(not(any(feature = "imap", feature = "msgraph", feature = "dav")))]
Client::Unavailable => bail!(NO_BACKEND),
}
}
pub fn fetch_summaries(&mut self, collection: &str, ids: &[&str]) -> Result<Vec<ItemSummary>> {
match self {
#[cfg(feature = "imap")]
Client::Imap(c) => c.fetch_envelopes(collection, ids),
#[cfg(feature = "msgraph")]
Client::Msgraph(c) => c.fetch_envelopes(collection, ids),
#[cfg(feature = "dav")]
Client::Dav(_) => bail!("DAV items have no summary tier (they resolve at Full)"),
#[cfg(not(any(feature = "imap", feature = "msgraph", feature = "dav")))]
Client::Unavailable => bail!(NO_BACKEND),
}
}
pub fn fetch_bodies<S: Write>(
&mut self,
collection: &str,
ids: &[&str],
open: impl FnMut(&str) -> std::io::Result<S>,
done: impl FnMut(&str, Option<&str>, S) -> std::io::Result<()>,
) -> Result<()> {
match self {
#[cfg(feature = "imap")]
Client::Imap(c) => c.fetch_bodies(collection, ids, open, done),
#[cfg(feature = "msgraph")]
Client::Msgraph(c) => c.fetch_bodies(collection, ids, open, done),
#[cfg(feature = "dav")]
Client::Dav(c) => c.fetch_bodies(collection, ids, open, done),
#[cfg(not(any(feature = "imap", feature = "msgraph", feature = "dav")))]
Client::Unavailable => bail!(NO_BACKEND),
}
}
pub fn get_item_stream(
&mut self,
collection: &str,
id: &str,
sink: impl Write,
) -> Result<Option<String>> {
match self {
#[cfg(feature = "imap")]
Client::Imap(c) => c.get_message_stream(collection, id, sink).map(|()| None),
#[cfg(feature = "msgraph")]
Client::Msgraph(c) => c.get_message_stream(collection, id, sink).map(|()| None),
#[cfg(feature = "dav")]
Client::Dav(c) => c.get_item_stream(collection, id, sink),
#[cfg(not(any(feature = "imap", feature = "msgraph", feature = "dav")))]
Client::Unavailable => bail!(NO_BACKEND),
}
}
pub fn add_item_stream(
&mut self,
collection: &str,
flags: &[Flag],
source: impl Read,
len: usize,
link: LinkId<'_>,
) -> Result<WrittenItem> {
match self {
#[cfg(feature = "imap")]
Client::Imap(c) => c
.add_message_stream(collection, flags, source, len, link.hint)
.map(|id| WrittenItem { id, revision: None }),
#[cfg(feature = "msgraph")]
Client::Msgraph(_) => bail!("Graph messages are pull-only (append not supported)"),
#[cfg(feature = "dav")]
Client::Dav(c) => c.add_item_stream(collection, source, link),
#[cfg(not(any(feature = "imap", feature = "msgraph", feature = "dav")))]
Client::Unavailable => bail!(NO_BACKEND),
}
}
#[allow(unused_variables)]
pub fn update_item_stream(
&mut self,
collection: &str,
id: &str,
source: impl Read,
len: usize,
if_match: Option<&str>,
) -> Result<Option<String>> {
match self {
#[cfg(feature = "imap")]
Client::Imap(_) => bail!("IMAP message bodies are immutable (in-place update)"),
#[cfg(feature = "msgraph")]
Client::Msgraph(_) => bail!("Graph message bodies are immutable (in-place update)"),
#[cfg(feature = "dav")]
Client::Dav(c) => c.update_item_stream(collection, id, source, if_match),
#[cfg(not(any(feature = "imap", feature = "msgraph", feature = "dav")))]
Client::Unavailable => bail!(NO_BACKEND),
}
}
#[allow(unused_variables)]
pub fn delete_item(
&mut self,
collection: &str,
id: &str,
if_match: Option<&str>,
) -> Result<()> {
match self {
#[cfg(feature = "imap")]
Client::Imap(c) => c.delete_message(collection, id),
#[cfg(feature = "msgraph")]
Client::Msgraph(c) => c.delete_message(id),
#[cfg(feature = "dav")]
Client::Dav(c) => c.delete_item(collection, id, if_match),
#[cfg(not(any(feature = "imap", feature = "msgraph", feature = "dav")))]
Client::Unavailable => bail!(NO_BACKEND),
}
}
pub fn move_items(&mut self, from: &str, to: &str, ids: &[&str]) -> Result<()> {
match self {
#[cfg(feature = "imap")]
Client::Imap(c) => c.move_messages(from, to, ids),
#[cfg(feature = "msgraph")]
Client::Msgraph(_) => bail!("Graph messages are pull-only (move not supported)"),
#[cfg(feature = "dav")]
Client::Dav(c) => c.move_items(from, to, ids),
#[cfg(not(any(feature = "imap", feature = "msgraph", feature = "dav")))]
Client::Unavailable => bail!(NO_BACKEND),
}
}
pub fn store_flags(
&mut self,
collection: &str,
ids: &[&str],
flags: &[Flag],
op: FlagOp,
) -> Result<()> {
match self {
#[cfg(feature = "imap")]
Client::Imap(c) => c.store_flags(collection, ids, flags, op),
#[cfg(feature = "msgraph")]
Client::Msgraph(c) => c.store_flags(ids, flags, op),
#[cfg(feature = "dav")]
Client::Dav(c) => c.store_flags(ids, flags, op),
#[cfg(not(any(feature = "imap", feature = "msgraph", feature = "dav")))]
Client::Unavailable => bail!(NO_BACKEND),
}
}
pub fn media_type(&self) -> &'static str {
match self {
#[cfg(feature = "imap")]
Client::Imap(_) => "message/rfc822",
#[cfg(feature = "msgraph")]
Client::Msgraph(_) => "message/rfc822",
#[cfg(feature = "dav")]
Client::Dav(c) => c.media_type(),
#[cfg(not(any(feature = "imap", feature = "msgraph", feature = "dav")))]
Client::Unavailable => "",
}
}
pub fn handle_space_epoch(&self, checkpoint: &[u8]) -> Option<u64> {
match self {
#[cfg(feature = "imap")]
Client::Imap(_) => {
crate::imap::backend::checkpoint_uid_validity(checkpoint).map(u64::from)
}
#[cfg(feature = "msgraph")]
Client::Msgraph(_) => None,
#[cfg(feature = "dav")]
Client::Dav(_) => None,
#[cfg(not(any(feature = "imap", feature = "msgraph", feature = "dav")))]
Client::Unavailable => None,
}
}
}
#[cfg_attr(
not(any(feature = "imap", feature = "msgraph", feature = "dav")),
allow(unused_variables)
)]
pub fn open(account: &SourceAccount) -> Result<Client> {
match &account.backend {
#[cfg(feature = "imap")]
SourceAccountBackend::Imap(imap) => {
let client =
ImapClient::connect(&imap.server, &imap.tls, imap.starttls, imap.sasl.clone())?;
Ok(Client::Imap(client))
}
#[cfg(feature = "msgraph")]
SourceAccountBackend::Msgraph(msgraph) => {
let client =
GraphClient::connect(&msgraph.token, &msgraph.user_id, msgraph.tls.clone())?;
Ok(Client::Msgraph(Box::new(client)))
}
#[cfg(feature = "dav")]
SourceAccountBackend::Dav(dav) => {
let client = DavClient::connect(dav.kind, &dav.server, &dav.tls, dav.auth.clone())?;
Ok(Client::Dav(Box::new(client)))
}
#[cfg(not(any(feature = "imap", feature = "msgraph", feature = "dav")))]
SourceAccountBackend::Unavailable => bail!(NO_BACKEND),
}
}
pub fn init(account: &SourceAccount) -> Result<Client> {
open(account)
}
pub struct Pool {
account: SourceAccount,
clients: Vec<Client>,
max: usize,
}
impl Pool {
pub fn open(account: SourceAccount, max: usize) -> Result<Self> {
let primary = open(&account)?;
Ok(Self {
account,
clients: vec![primary],
max: max.max(1),
})
}
pub fn max(&self) -> usize {
self.max
}
pub fn primary(&mut self) -> &mut Client {
&mut self.clients[0]
}
pub fn workers(&mut self, n: usize) -> Result<&mut [Client]> {
let want = n.min(self.max);
while self.clients.len() < want {
self.clients.push(open(&self.account)?);
}
let take = want.min(self.clients.len());
Ok(&mut self.clients[..take])
}
}