ashpd/desktop/
email.rs

1//! Compose an email.
2//!
3//! Wrapper of the DBus interface: [`org.freedesktop.portal.Email`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Email.html).
4//!
5//! # Examples
6//!
7//! Compose an email
8//!
9//! ```rust,no_run
10//! use std::{fs::File, os::fd::OwnedFd};
11//!
12//! use ashpd::desktop::email::EmailRequest;
13//!
14//! async fn run() -> ashpd::Result<()> {
15//!     let file = File::open("/home/bilelmoussaoui/Downloads/adwaita-night.jpg").unwrap();
16//!     EmailRequest::default()
17//!         .address("test@gmail.com")
18//!         .subject("email subject")
19//!         .body("the pre-filled email body")
20//!         .attach(OwnedFd::from(file))
21//!         .send()
22//!         .await;
23//!     Ok(())
24//! }
25//! ```
26
27use std::os::fd::OwnedFd;
28
29use serde::Serialize;
30use zbus::zvariant::{self, SerializeDict, Type};
31
32use super::{HandleToken, Request};
33use crate::{proxy::Proxy, ActivationToken, Error, WindowIdentifier};
34
35#[derive(SerializeDict, Type, Debug, Default)]
36#[zvariant(signature = "dict")]
37struct EmailOptions {
38    handle_token: HandleToken,
39    address: Option<String>,
40    addresses: Option<Vec<String>>,
41    cc: Option<Vec<String>>,
42    bcc: Option<Vec<String>>,
43    subject: Option<String>,
44    body: Option<String>,
45    attachment_fds: Option<Vec<zvariant::OwnedFd>>,
46    activation_token: Option<ActivationToken>,
47}
48
49#[derive(Debug)]
50#[doc(alias = "org.freedesktop.portal.Email")]
51struct EmailProxy<'a>(Proxy<'a>);
52
53impl<'a> EmailProxy<'a> {
54    /// Create a new instance of [`EmailProxy`].
55    pub async fn new() -> Result<EmailProxy<'a>, Error> {
56        let proxy = Proxy::new_desktop("org.freedesktop.portal.Email").await?;
57        Ok(Self(proxy))
58    }
59
60    /// Presents a window that lets the user compose an email.
61    ///
62    /// **Note** the default email client for the host will need to support
63    /// `mailto:` URIs following RFC 2368.
64    ///
65    /// # Arguments
66    ///
67    /// * `identifier` - Identifier for the application window.
68    /// * `options` - An [`EmailOptions`].
69    ///
70    /// # Specifications
71    ///
72    /// See also [`ComposeEmail`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Email.html#org-freedesktop-portal-email-composeemail).
73    #[doc(alias = "ComposeEmail")]
74    pub async fn compose(
75        &self,
76        identifier: Option<&WindowIdentifier>,
77        options: EmailOptions,
78    ) -> Result<Request<()>, Error> {
79        let identifier = identifier.map(|i| i.to_string()).unwrap_or_default();
80        self.0
81            .empty_request(
82                &options.handle_token,
83                "ComposeEmail",
84                &(&identifier, &options),
85            )
86            .await
87    }
88}
89
90impl<'a> std::ops::Deref for EmailProxy<'a> {
91    type Target = zbus::Proxy<'a>;
92
93    fn deref(&self) -> &Self::Target {
94        &self.0
95    }
96}
97
98#[derive(Debug, Default)]
99#[doc(alias = "xdp_portal_compose_email")]
100/// A [builder-pattern] type to compose an email.
101///
102/// [builder-pattern]: https://doc.rust-lang.org/1.0.0/style/ownership/builders.html
103pub struct EmailRequest {
104    identifier: Option<WindowIdentifier>,
105    options: EmailOptions,
106}
107
108impl EmailRequest {
109    /// Sets a window identifier.
110    #[must_use]
111    pub fn identifier(mut self, identifier: impl Into<Option<WindowIdentifier>>) -> Self {
112        self.identifier = identifier.into();
113        self
114    }
115
116    /// Sets the email address to send the email to.
117    #[must_use]
118    pub fn address<'a>(mut self, address: impl Into<Option<&'a str>>) -> Self {
119        self.options.address = address.into().map(ToOwned::to_owned);
120        self
121    }
122
123    /// Sets a list of email addresses to send the email to.
124    #[must_use]
125    pub fn addresses<P: IntoIterator<Item = I>, I: AsRef<str> + Type + Serialize>(
126        mut self,
127        addresses: impl Into<Option<P>>,
128    ) -> Self {
129        self.options.addresses = addresses
130            .into()
131            .map(|a| a.into_iter().map(|s| s.as_ref().to_owned()).collect());
132        self
133    }
134
135    /// Sets a list of email addresses to BCC.
136    #[must_use]
137    pub fn bcc<P: IntoIterator<Item = I>, I: AsRef<str> + Type + Serialize>(
138        mut self,
139        bcc: impl Into<Option<P>>,
140    ) -> Self {
141        self.options.bcc = bcc
142            .into()
143            .map(|a| a.into_iter().map(|s| s.as_ref().to_owned()).collect());
144        self
145    }
146
147    /// Sets a list of email addresses to CC.
148    #[must_use]
149    pub fn cc<P: IntoIterator<Item = I>, I: AsRef<str> + Type + Serialize>(
150        mut self,
151        cc: impl Into<Option<P>>,
152    ) -> Self {
153        self.options.cc = cc
154            .into()
155            .map(|a| a.into_iter().map(|s| s.as_ref().to_owned()).collect());
156        self
157    }
158
159    /// Sets the email subject.
160    #[must_use]
161    pub fn subject<'a>(mut self, subject: impl Into<Option<&'a str>>) -> Self {
162        self.options.subject = subject.into().map(ToOwned::to_owned);
163        self
164    }
165
166    /// Sets the email body.
167    #[must_use]
168    pub fn body<'a>(mut self, body: impl Into<Option<&'a str>>) -> Self {
169        self.options.body = body.into().map(ToOwned::to_owned);
170        self
171    }
172
173    /// Attaches a file to the email.
174    #[must_use]
175    pub fn attach(mut self, attachment: OwnedFd) -> Self {
176        self.add_attachment(attachment);
177        self
178    }
179
180    // TODO Added in version 4 of the interface.
181    /// Sets the token that can be used to activate the chosen application.
182    #[must_use]
183    pub fn activation_token(
184        mut self,
185        activation_token: impl Into<Option<ActivationToken>>,
186    ) -> Self {
187        self.options.activation_token = activation_token.into();
188        self
189    }
190
191    /// A different variant of [`Self::attach`].
192    pub fn add_attachment(&mut self, attachment: OwnedFd) {
193        let attachment = zvariant::OwnedFd::from(attachment);
194        match self.options.attachment_fds {
195            Some(ref mut attachments) => attachments.push(attachment),
196            None => {
197                self.options.attachment_fds.replace(vec![attachment]);
198            }
199        };
200    }
201
202    /// Send the request.
203    pub async fn send(self) -> Result<Request<()>, Error> {
204        let proxy = EmailProxy::new().await?;
205        proxy.compose(self.identifier.as_ref(), self.options).await
206    }
207}