email/backend/
context.rs

1//! # Backend context
2//!
3//! The [`BackendContext`] is usually used for storing clients or
4//! sessions (structures than cannot be cloned or sync). The
5//! [`BackendContextBuilder`] gives instructions on how to build such
6//! context. It is used by the backend builder.
7
8use async_trait::async_trait;
9use paste::paste;
10
11use super::feature::{BackendFeature, CheckUp};
12#[cfg(feature = "thread")]
13use crate::envelope::thread::ThreadEnvelopes;
14#[cfg(feature = "watch")]
15use crate::envelope::watch::WatchEnvelopes;
16use crate::{
17    envelope::{get::GetEnvelope, list::ListEnvelopes},
18    flag::{add::AddFlags, remove::RemoveFlags, set::SetFlags},
19    folder::{
20        add::AddFolder, delete::DeleteFolder, expunge::ExpungeFolder, list::ListFolders,
21        purge::PurgeFolder,
22    },
23    message::{
24        add::AddMessage, copy::CopyMessages, delete::DeleteMessages, get::GetMessages,
25        peek::PeekMessages, r#move::MoveMessages, remove::RemoveMessages, send::SendMessage,
26    },
27    AnyResult,
28};
29
30/// The backend context.
31///
32/// This is just a marker for other backend traits. Every backend
33/// context needs to implement this trait manually or to derive
34/// [`crate::backend_v2::macros::BackendContextV2`].
35pub trait BackendContext: Send + Sync {}
36
37/// Macro for defining [`BackendContextBuilder`] features.
38macro_rules! feature {
39    ($feat:ty) => {
40        paste! {
41            /// Define the given backend feature.
42            fn [<$feat:snake>](&self) -> Option<BackendFeature<Self::Context, dyn $feat>> {
43                None
44            }
45        }
46    };
47}
48
49/// The backend context builder.
50///
51/// This trait defines how a context should be built. It also defines
52/// default backend features implemented by the context itself.
53#[async_trait]
54pub trait BackendContextBuilder: Clone + Send + Sync {
55    /// The type of the context being built by this builder.
56    type Context: BackendContext;
57
58    async fn check(&self) -> AnyResult<()> {
59        if let Some(feature) = self.check_up() {
60            let ctx = self.clone().build().await?;
61
62            if let Some(feature) = feature(&ctx) {
63                feature.check_up().await?;
64            }
65        }
66
67        Ok(())
68    }
69
70    fn check_configuration(&self) -> AnyResult<()> {
71        Ok(())
72    }
73
74    async fn configure(&mut self) -> AnyResult<()> {
75        Ok(())
76    }
77
78    feature!(CheckUp);
79
80    feature!(AddFolder);
81    feature!(ListFolders);
82    feature!(ExpungeFolder);
83    feature!(PurgeFolder);
84    feature!(DeleteFolder);
85    feature!(GetEnvelope);
86    feature!(ListEnvelopes);
87    #[cfg(feature = "thread")]
88    feature!(ThreadEnvelopes);
89    #[cfg(feature = "watch")]
90    feature!(WatchEnvelopes);
91    feature!(AddFlags);
92    feature!(SetFlags);
93    feature!(RemoveFlags);
94    feature!(AddMessage);
95    feature!(SendMessage);
96    feature!(PeekMessages);
97    feature!(GetMessages);
98    feature!(CopyMessages);
99    feature!(MoveMessages);
100    feature!(DeleteMessages);
101    feature!(RemoveMessages);
102
103    /// Build the final context used by the backend.
104    async fn build(self) -> AnyResult<Self::Context>;
105
106    #[cfg(feature = "sync")]
107    fn try_to_sync_cache_builder(
108        &self,
109        account_config: &crate::account::config::AccountConfig,
110    ) -> std::result::Result<crate::maildir::MaildirContextBuilder, crate::account::Error>
111    where
112        Self: crate::sync::hash::SyncHash,
113    {
114        use std::{
115            hash::{DefaultHasher, Hasher},
116            sync::Arc,
117        };
118
119        use dirs::data_dir;
120        use shellexpand_utils::try_shellexpand_path;
121        use tracing::debug;
122
123        use crate::{
124            account::{config::AccountConfig, Error},
125            maildir::{config::MaildirConfig, MaildirContextBuilder},
126        };
127
128        let mut hasher = DefaultHasher::new();
129        self.sync_hash(&mut hasher);
130        let hash = format!("{:x}", hasher.finish());
131
132        let sync_dir = account_config.sync.as_ref().and_then(|c| c.dir.as_ref());
133        let root_dir = match sync_dir {
134            Some(dir) => {
135                let dir = try_shellexpand_path(dir)
136                    .map_err(|err| Error::GetSyncDirInvalidError(err, dir.clone()))?;
137                debug!(?dir, "using custom sync directory");
138                dir
139            }
140            None => {
141                let dir = data_dir()
142                    .ok_or(Error::GetXdgDataDirSyncError)?
143                    .join("pimalaya")
144                    .join("email")
145                    .join("sync")
146                    .join(&hash);
147                debug!(?dir, "using default sync directory");
148                dir
149            }
150        };
151
152        let account_config = Arc::new(AccountConfig {
153            name: account_config.name.clone(),
154            email: account_config.email.clone(),
155            display_name: account_config.display_name.clone(),
156            signature: account_config.signature.clone(),
157            signature_delim: account_config.signature_delim.clone(),
158            downloads_dir: account_config.downloads_dir.clone(),
159            folder: account_config.folder.clone(),
160            envelope: account_config.envelope.clone(),
161            flag: account_config.flag.clone(),
162            message: account_config.message.clone(),
163            template: account_config.template.clone(),
164            sync: None,
165            #[cfg(feature = "pgp")]
166            pgp: account_config.pgp.clone(),
167        });
168
169        let config = Arc::new(MaildirConfig {
170            root_dir,
171            maildirpp: false,
172        });
173
174        let ctx = MaildirContextBuilder::new(account_config.clone(), config);
175
176        Ok(ctx)
177    }
178}