Add smtp client support for sending mail in UI
`mailer_command` was removed, and a new setting `send_mail` was added. Its possible values are a string, consisting of a shell command to execute, or settings to configure an smtp server connection. The configuration I used for testing this is: [composing] send_mail = { hostname = "smtp.mail.tld", port = 587, auth = { type = "auto", username = "yoshi", password = { type = "command_eval", value = "gpg2 --no-tty -q -d ~/.passwords/msmtp/yoshi.gpg" } }, security = { type = "STARTTLS" } } For local smtp server: [composing] send_mail = { hostname = "localhost", port = 25, auth = { type = "none" }, security = { type = "none" } }master
parent
ddafde7b37
commit
77dc1d74bf
|
@ -69,10 +69,11 @@ debug = false
|
||||||
members = ["melib", "testing", ]
|
members = ["melib", "testing", ]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["sqlite3", "notmuch", "regexp"]
|
default = ["sqlite3", "notmuch", "regexp", "smtp"]
|
||||||
notmuch = ["melib/notmuch_backend", ]
|
notmuch = ["melib/notmuch_backend", ]
|
||||||
jmap = ["melib/jmap_backend",]
|
jmap = ["melib/jmap_backend",]
|
||||||
sqlite3 = ["melib/sqlite3"]
|
sqlite3 = ["melib/sqlite3"]
|
||||||
|
smtp = ["melib/smtp"]
|
||||||
regexp = ["pcre2"]
|
regexp = ["pcre2"]
|
||||||
cli-docs = []
|
cli-docs = []
|
||||||
svgscreenshot = ["svg_crate"]
|
svgscreenshot = ["svg_crate"]
|
||||||
|
|
|
@ -79,7 +79,7 @@ thread_local!(static LOG: Arc<Mutex<LoggingBackend>> = Arc::new(Mutex::new({
|
||||||
}}))
|
}}))
|
||||||
);
|
);
|
||||||
|
|
||||||
pub fn log(val: String, level: LoggingLevel) {
|
pub fn log<S: AsRef<str>>(val: S, level: LoggingLevel) {
|
||||||
LOG.with(|f| {
|
LOG.with(|f| {
|
||||||
let mut b = f.lock().unwrap();
|
let mut b = f.lock().unwrap();
|
||||||
if level <= b.level {
|
if level <= b.level {
|
||||||
|
@ -91,7 +91,7 @@ pub fn log(val: String, level: LoggingLevel) {
|
||||||
b.dest.write_all(b" [").unwrap();
|
b.dest.write_all(b" [").unwrap();
|
||||||
b.dest.write_all(level.to_string().as_bytes()).unwrap();
|
b.dest.write_all(level.to_string().as_bytes()).unwrap();
|
||||||
b.dest.write_all(b"]: ").unwrap();
|
b.dest.write_all(b"]: ").unwrap();
|
||||||
b.dest.write_all(val.as_bytes()).unwrap();
|
b.dest.write_all(val.as_ref().as_bytes()).unwrap();
|
||||||
b.dest.write_all(b"\n").unwrap();
|
b.dest.write_all(b"\n").unwrap();
|
||||||
b.dest.flush().unwrap();
|
b.dest.flush().unwrap();
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ use melib::list_management;
|
||||||
use melib::Draft;
|
use melib::Draft;
|
||||||
|
|
||||||
use crate::conf::accounts::JobRequest;
|
use crate::conf::accounts::JobRequest;
|
||||||
use crate::jobs::{oneshot, JobId};
|
use crate::jobs::{JobChannel, JobId, JoinHandle};
|
||||||
use crate::terminal::embed::EmbedGrid;
|
use crate::terminal::embed::EmbedGrid;
|
||||||
use nix::sys::wait::WaitStatus;
|
use nix::sys::wait::WaitStatus;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
@ -66,7 +66,7 @@ impl std::ops::DerefMut for EmbedStatus {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Composer {
|
pub struct Composer {
|
||||||
reply_context: Option<(MailboxHash, EnvelopeHash)>,
|
reply_context: Option<(MailboxHash, EnvelopeHash)>,
|
||||||
reply_bytes_request: Option<(JobId, oneshot::Receiver<Result<Vec<u8>>>)>,
|
reply_bytes_request: Option<(JobId, JobChannel<Vec<u8>>)>,
|
||||||
account_cursor: usize,
|
account_cursor: usize,
|
||||||
|
|
||||||
cursor: Cursor,
|
cursor: Cursor,
|
||||||
|
@ -120,6 +120,7 @@ enum ViewMode {
|
||||||
Embed,
|
Embed,
|
||||||
SelectRecipients(UIDialog<Address>),
|
SelectRecipients(UIDialog<Address>),
|
||||||
Send(UIConfirmationDialog),
|
Send(UIConfirmationDialog),
|
||||||
|
WaitingForSendResult(UIDialog<char>, JoinHandle, JobId, JobChannel<()>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ViewMode {
|
impl ViewMode {
|
||||||
|
@ -612,6 +613,10 @@ impl Component for Composer {
|
||||||
/* Let user choose whether to quit with/without saving or cancel */
|
/* Let user choose whether to quit with/without saving or cancel */
|
||||||
s.draw(grid, center_area(area, s.content.size()), context);
|
s.draw(grid, center_area(area, s.content.size()), context);
|
||||||
}
|
}
|
||||||
|
ViewMode::WaitingForSendResult(ref mut s, _, _, _) => {
|
||||||
|
/* Let user choose whether to wait for success or cancel */
|
||||||
|
s.draw(grid, center_area(area, s.content.size()), context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self.dirty = false;
|
self.dirty = false;
|
||||||
self.draw_attachments(grid, attachment_area, context);
|
self.draw_attachments(grid, attachment_area, context);
|
||||||
|
@ -673,18 +678,49 @@ impl Component for Composer {
|
||||||
{
|
{
|
||||||
if let Some(true) = result.downcast_ref::<bool>() {
|
if let Some(true) = result.downcast_ref::<bool>() {
|
||||||
self.update_draft();
|
self.update_draft();
|
||||||
if send_draft(
|
match send_draft(
|
||||||
self.sign_mail,
|
self.sign_mail,
|
||||||
context,
|
context,
|
||||||
self.account_cursor,
|
self.account_cursor,
|
||||||
self.draft.clone(),
|
self.draft.clone(),
|
||||||
SpecialUsageMailbox::Sent,
|
SpecialUsageMailbox::Sent,
|
||||||
Flag::SEEN,
|
Flag::SEEN,
|
||||||
|
false,
|
||||||
) {
|
) {
|
||||||
|
Ok(Some((job_id, handle, chan))) => {
|
||||||
|
self.mode = ViewMode::WaitingForSendResult(
|
||||||
|
UIDialog::new(
|
||||||
|
"Waiting for confirmation.. The tab will close automatically on successful submission.",
|
||||||
|
vec![
|
||||||
|
('c', "force close tab".to_string()),
|
||||||
|
('n', "close this message and return to edit mode".to_string()),
|
||||||
|
],
|
||||||
|
true,
|
||||||
|
Some(Box::new(move |id: ComponentId, results: &[char]| {
|
||||||
|
Some(UIEvent::FinishedUIDialog(
|
||||||
|
id,
|
||||||
|
Box::new(results.get(0).map(|c| *c).unwrap_or('c')),
|
||||||
|
))
|
||||||
|
})),
|
||||||
|
context,
|
||||||
|
), handle, job_id, chan);
|
||||||
|
}
|
||||||
|
Ok(None) => {
|
||||||
|
context.replies.push_back(UIEvent::Notification(
|
||||||
|
Some("Sent.".into()),
|
||||||
|
String::new(),
|
||||||
|
Some(NotificationType::INFO),
|
||||||
|
));
|
||||||
context
|
context
|
||||||
.replies
|
.replies
|
||||||
.push_back(UIEvent::Action(Tab(Kill(self.id))));
|
.push_back(UIEvent::Action(Tab(Kill(self.id))));
|
||||||
} else {
|
}
|
||||||
|
Err(err) => {
|
||||||
|
context.replies.push_back(UIEvent::Notification(
|
||||||
|
None,
|
||||||
|
err.to_string(),
|
||||||
|
Some(NotificationType::ERROR),
|
||||||
|
));
|
||||||
save_draft(
|
save_draft(
|
||||||
self.draft.clone().finalise().unwrap().as_bytes(),
|
self.draft.clone().finalise().unwrap().as_bytes(),
|
||||||
context,
|
context,
|
||||||
|
@ -692,9 +728,11 @@ impl Component for Composer {
|
||||||
Flag::SEEN | Flag::DRAFT,
|
Flag::SEEN | Flag::DRAFT,
|
||||||
self.account_cursor,
|
self.account_cursor,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
|
||||||
self.mode = ViewMode::Edit;
|
self.mode = ViewMode::Edit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.set_dirty(true);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
(ViewMode::Send(ref mut selector), _) => {
|
(ViewMode::Send(ref mut selector), _) => {
|
||||||
|
@ -753,6 +791,59 @@ impl Component for Composer {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
(
|
||||||
|
ViewMode::WaitingForSendResult(ref selector, _, _, _),
|
||||||
|
UIEvent::FinishedUIDialog(id, result),
|
||||||
|
) if selector.id() == *id => {
|
||||||
|
if let Some(key) = result.downcast_mut::<char>() {
|
||||||
|
match key {
|
||||||
|
'c' => {
|
||||||
|
context
|
||||||
|
.replies
|
||||||
|
.push_back(UIEvent::Action(Tab(Kill(self.id))));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
'n' => {
|
||||||
|
self.set_dirty(true);
|
||||||
|
if let ViewMode::WaitingForSendResult(_, handle, job_id, chan) =
|
||||||
|
std::mem::replace(&mut self.mode, ViewMode::Edit)
|
||||||
|
{
|
||||||
|
context.accounts[self.account_cursor].active_jobs.insert(
|
||||||
|
job_id,
|
||||||
|
JobRequest::SendMessageBackground(handle, chan),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
(
|
||||||
|
ViewMode::WaitingForSendResult(_, _, ref our_job_id, ref mut chan),
|
||||||
|
UIEvent::StatusEvent(StatusEvent::JobFinished(ref job_id)),
|
||||||
|
) if *our_job_id == *job_id => {
|
||||||
|
let result = chan.try_recv().unwrap();
|
||||||
|
if let Some(Err(err)) = result {
|
||||||
|
self.mode = ViewMode::Edit;
|
||||||
|
context.replies.push_back(UIEvent::Notification(
|
||||||
|
None,
|
||||||
|
err.to_string(),
|
||||||
|
Some(NotificationType::ERROR),
|
||||||
|
));
|
||||||
|
self.set_dirty(true);
|
||||||
|
} else {
|
||||||
|
context
|
||||||
|
.replies
|
||||||
|
.push_back(UIEvent::Action(Tab(Kill(self.id))));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
(ViewMode::WaitingForSendResult(ref mut selector, _, _, _), _) => {
|
||||||
|
if selector.process_event(event, context) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
if self.cursor == Cursor::Headers
|
if self.cursor == Cursor::Headers
|
||||||
|
@ -1279,28 +1370,9 @@ pub fn send_draft(
|
||||||
mut draft: Draft,
|
mut draft: Draft,
|
||||||
mailbox_type: SpecialUsageMailbox,
|
mailbox_type: SpecialUsageMailbox,
|
||||||
flags: Flag,
|
flags: Flag,
|
||||||
) -> bool {
|
complete_in_background: bool,
|
||||||
use std::io::Write;
|
) -> Result<Option<(JobId, JoinHandle, JobChannel<()>)>> {
|
||||||
use std::process::{Command, Stdio};
|
|
||||||
let format_flowed = *mailbox_acc_settings!(context[account_cursor].composing.format_flowed);
|
let format_flowed = *mailbox_acc_settings!(context[account_cursor].composing.format_flowed);
|
||||||
let command = mailbox_acc_settings!(context[account_cursor].composing.mailer_command);
|
|
||||||
if command.is_empty() {
|
|
||||||
context.replies.push_back(UIEvent::Notification(
|
|
||||||
None,
|
|
||||||
String::from("mailer_command configuration value is empty"),
|
|
||||||
Some(NotificationType::ERROR),
|
|
||||||
));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
let bytes;
|
|
||||||
let mut msmtp = Command::new("sh")
|
|
||||||
.args(&["-c", command])
|
|
||||||
.stdin(Stdio::piped())
|
|
||||||
.stdout(Stdio::piped())
|
|
||||||
.spawn()
|
|
||||||
.expect("Failed to start mailer command");
|
|
||||||
{
|
|
||||||
let stdin = msmtp.stdin.as_mut().expect("failed to open stdin");
|
|
||||||
if sign_mail.is_true() {
|
if sign_mail.is_true() {
|
||||||
let mut content_type = ContentType::default();
|
let mut content_type = ContentType::default();
|
||||||
if format_flowed {
|
if format_flowed {
|
||||||
|
@ -1342,13 +1414,14 @@ pub fn send_draft(
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|s| s.as_str()),
|
.map(|s| s.as_str()),
|
||||||
);
|
);
|
||||||
if let Err(e) = &output {
|
match output {
|
||||||
debug!("{:?} could not sign draft msg", e);
|
Err(err) => {
|
||||||
|
debug!("{:?} could not sign draft msg", err);
|
||||||
log(
|
log(
|
||||||
format!(
|
format!(
|
||||||
"Could not sign draft in account `{}`: {}.",
|
"Could not sign draft in account `{}`: {}.",
|
||||||
context.accounts[account_cursor].name(),
|
context.accounts[account_cursor].name(),
|
||||||
e.to_string()
|
err.to_string()
|
||||||
),
|
),
|
||||||
ERROR,
|
ERROR,
|
||||||
);
|
);
|
||||||
|
@ -1357,12 +1430,15 @@ pub fn send_draft(
|
||||||
"Could not sign draft in account `{}`.",
|
"Could not sign draft in account `{}`.",
|
||||||
context.accounts[account_cursor].name()
|
context.accounts[account_cursor].name()
|
||||||
)),
|
)),
|
||||||
e.to_string(),
|
err.to_string(),
|
||||||
Some(NotificationType::ERROR),
|
Some(NotificationType::ERROR),
|
||||||
));
|
));
|
||||||
return false;
|
return Err(err);
|
||||||
|
}
|
||||||
|
Ok(output) => {
|
||||||
|
draft.attachments.push(output);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
draft.attachments.push(output.unwrap());
|
|
||||||
} else {
|
} else {
|
||||||
let mut content_type = ContentType::default();
|
let mut content_type = ContentType::default();
|
||||||
if format_flowed {
|
if format_flowed {
|
||||||
|
@ -1382,37 +1458,10 @@ pub fn send_draft(
|
||||||
draft.attachments.insert(0, body);
|
draft.attachments.insert(0, body);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bytes = draft.finalise().unwrap();
|
let bytes = draft.finalise().unwrap();
|
||||||
stdin
|
let send_mail = mailbox_acc_settings!(context[account_cursor].composing.send_mail).clone();
|
||||||
.write_all(bytes.as_bytes())
|
let ret =
|
||||||
.expect("Failed to write to stdin");
|
context.accounts[account_cursor].send(bytes.clone(), send_mail, complete_in_background);
|
||||||
}
|
|
||||||
let output = msmtp.wait().expect("Failed to wait on mailer");
|
|
||||||
if output.success() {
|
|
||||||
context.replies.push_back(UIEvent::Notification(
|
|
||||||
Some("Sent.".into()),
|
|
||||||
String::new(),
|
|
||||||
None,
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
let error_message = if let Some(exit_code) = output.code() {
|
|
||||||
format!(
|
|
||||||
"Could not send e-mail using `{}`: Process exited with {}",
|
|
||||||
command, exit_code
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
format!(
|
|
||||||
"Could not send e-mail using `{}`: Process was killed by signal",
|
|
||||||
command
|
|
||||||
)
|
|
||||||
};
|
|
||||||
context.replies.push_back(UIEvent::Notification(
|
|
||||||
Some("Message not sent.".into()),
|
|
||||||
error_message.clone(),
|
|
||||||
Some(NotificationType::ERROR),
|
|
||||||
));
|
|
||||||
log(error_message, ERROR);
|
|
||||||
}
|
|
||||||
save_draft(
|
save_draft(
|
||||||
bytes.as_bytes(),
|
bytes.as_bytes(),
|
||||||
context,
|
context,
|
||||||
|
@ -1420,7 +1469,7 @@ pub fn send_draft(
|
||||||
flags,
|
flags,
|
||||||
account_cursor,
|
account_cursor,
|
||||||
);
|
);
|
||||||
true
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn save_draft(
|
pub fn save_draft(
|
||||||
|
|
|
@ -1495,14 +1495,23 @@ impl Component for MailView {
|
||||||
* on its own */
|
* on its own */
|
||||||
drop(detect);
|
drop(detect);
|
||||||
drop(envelope);
|
drop(envelope);
|
||||||
return super::compose::send_draft(
|
if let Err(err) = super::compose::send_draft(
|
||||||
ToggleFlag::False,
|
ToggleFlag::False,
|
||||||
context,
|
context,
|
||||||
self.coordinates.0,
|
self.coordinates.0,
|
||||||
draft,
|
draft,
|
||||||
SpecialUsageMailbox::Sent,
|
SpecialUsageMailbox::Sent,
|
||||||
Flag::SEEN,
|
Flag::SEEN,
|
||||||
);
|
true,
|
||||||
|
) {
|
||||||
|
context.replies.push_back(UIEvent::StatusEvent(
|
||||||
|
StatusEvent::DisplayMessage(format!(
|
||||||
|
"Couldn't send unsubscribe e-mail: {}",
|
||||||
|
err
|
||||||
|
)),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
list_management::ListAction::Url(url) => {
|
list_management::ListAction::Url(url) => {
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use super::{AccountConf, FileMailboxConf};
|
use super::{AccountConf, FileMailboxConf};
|
||||||
use crate::jobs::{JobExecutor, JobId, JoinHandle};
|
use crate::jobs::{JobChannel, JobExecutor, JobId, JoinHandle};
|
||||||
use melib::async_workers::{Async, AsyncBuilder, AsyncStatus, WorkContext};
|
use melib::async_workers::{Async, AsyncBuilder, AsyncStatus, WorkContext};
|
||||||
use melib::backends::{
|
use melib::backends::{
|
||||||
AccountHash, BackendOp, Backends, MailBackend, Mailbox, MailboxHash, NotifyFn, ReadOnlyOp,
|
AccountHash, BackendOp, Backends, MailBackend, Mailbox, MailboxHash, NotifyFn, ReadOnlyOp,
|
||||||
|
@ -173,7 +173,8 @@ pub enum JobRequest {
|
||||||
Refresh(MailboxHash, JoinHandle, oneshot::Receiver<Result<()>>),
|
Refresh(MailboxHash, JoinHandle, oneshot::Receiver<Result<()>>),
|
||||||
SetFlags(EnvelopeHash, JoinHandle, oneshot::Receiver<Result<()>>),
|
SetFlags(EnvelopeHash, JoinHandle, oneshot::Receiver<Result<()>>),
|
||||||
SaveMessage(MailboxHash, JoinHandle, oneshot::Receiver<Result<()>>),
|
SaveMessage(MailboxHash, JoinHandle, oneshot::Receiver<Result<()>>),
|
||||||
SendMessage(JoinHandle, oneshot::Receiver<Result<()>>),
|
SendMessage,
|
||||||
|
SendMessageBackground(JoinHandle, JobChannel<()>),
|
||||||
CopyTo(MailboxHash, JoinHandle, oneshot::Receiver<Result<Vec<u8>>>),
|
CopyTo(MailboxHash, JoinHandle, oneshot::Receiver<Result<Vec<u8>>>),
|
||||||
DeleteMessage(EnvelopeHash, JoinHandle, oneshot::Receiver<Result<()>>),
|
DeleteMessage(EnvelopeHash, JoinHandle, oneshot::Receiver<Result<()>>),
|
||||||
CreateMailbox(
|
CreateMailbox(
|
||||||
|
@ -215,7 +216,10 @@ impl core::fmt::Debug for JobRequest {
|
||||||
write!(f, "JobRequest::SetMailboxSubscription")
|
write!(f, "JobRequest::SetMailboxSubscription")
|
||||||
}
|
}
|
||||||
JobRequest::Watch(_) => write!(f, "JobRequest::Watch"),
|
JobRequest::Watch(_) => write!(f, "JobRequest::Watch"),
|
||||||
JobRequest::SendMessage(_, _) => write!(f, "JobRequest::SendMessage"),
|
JobRequest::SendMessage => write!(f, "JobRequest::SendMessage"),
|
||||||
|
JobRequest::SendMessageBackground(_, _) => {
|
||||||
|
write!(f, "JobRequest::SendMessageBackground")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1221,6 +1225,81 @@ impl Account {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn send(
|
||||||
|
&mut self,
|
||||||
|
message: String,
|
||||||
|
send_mail: crate::conf::composing::SendMail,
|
||||||
|
complete_in_background: bool,
|
||||||
|
) -> Result<Option<(JobId, JoinHandle, JobChannel<()>)>> {
|
||||||
|
use crate::conf::composing::SendMail;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::process::{Command, Stdio};
|
||||||
|
debug!(&send_mail);
|
||||||
|
match send_mail {
|
||||||
|
SendMail::ShellCommand(ref command) => {
|
||||||
|
if command.is_empty() {
|
||||||
|
return Err(MeliError::new(
|
||||||
|
"send_mail shell command configuration value is empty",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let mut msmtp = Command::new("sh")
|
||||||
|
.args(&["-c", command])
|
||||||
|
.stdin(Stdio::piped())
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.spawn()
|
||||||
|
.expect("Failed to start mailer command");
|
||||||
|
{
|
||||||
|
let stdin = msmtp.stdin.as_mut().expect("failed to open stdin");
|
||||||
|
stdin
|
||||||
|
.write_all(message.as_bytes())
|
||||||
|
.expect("Failed to write to stdin");
|
||||||
|
}
|
||||||
|
let output = msmtp.wait().expect("Failed to wait on mailer");
|
||||||
|
if output.success() {
|
||||||
|
melib::log("Message sent.", melib::LoggingLevel::TRACE);
|
||||||
|
} else {
|
||||||
|
let error_message = if let Some(exit_code) = output.code() {
|
||||||
|
format!(
|
||||||
|
"Could not send e-mail using `{}`: Process exited with {}",
|
||||||
|
command, exit_code
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
format!(
|
||||||
|
"Could not send e-mail using `{}`: Process was killed by signal",
|
||||||
|
command
|
||||||
|
)
|
||||||
|
};
|
||||||
|
melib::log(&error_message, melib::LoggingLevel::ERROR);
|
||||||
|
return Err(
|
||||||
|
MeliError::new(error_message.clone()).set_summary("Message not sent.")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
#[cfg(feature = "smtp")]
|
||||||
|
SendMail::Smtp(conf) => {
|
||||||
|
let (chan, handle, job_id) = self.job_executor.spawn_specialized(async move {
|
||||||
|
let mut smtp_connection =
|
||||||
|
melib::smtp::SmtpConnection::new_connection(conf).await?;
|
||||||
|
smtp_connection.mail_transaction(&message).await
|
||||||
|
});
|
||||||
|
self.sender
|
||||||
|
.send(ThreadEvent::UIEvent(UIEvent::StatusEvent(
|
||||||
|
StatusEvent::NewJob(job_id),
|
||||||
|
)))
|
||||||
|
.unwrap();
|
||||||
|
if complete_in_background {
|
||||||
|
self.active_jobs
|
||||||
|
.insert(job_id, JobRequest::SendMessageBackground(handle, chan));
|
||||||
|
return Ok(None);
|
||||||
|
} else {
|
||||||
|
self.active_jobs.insert(job_id, JobRequest::SendMessage);
|
||||||
|
}
|
||||||
|
Ok(Some((job_id, handle, chan)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn delete(
|
pub fn delete(
|
||||||
&mut self,
|
&mut self,
|
||||||
env_hash: EnvelopeHash,
|
env_hash: EnvelopeHash,
|
||||||
|
@ -1678,6 +1757,30 @@ impl Account {
|
||||||
.expect("Could not send event on main channel");
|
.expect("Could not send event on main channel");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
JobRequest::SendMessage => {
|
||||||
|
self.sender
|
||||||
|
.send(ThreadEvent::UIEvent(UIEvent::StatusEvent(
|
||||||
|
StatusEvent::JobFinished(*job_id),
|
||||||
|
)))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
JobRequest::SendMessageBackground(_, mut chan) => {
|
||||||
|
let r = chan.try_recv().unwrap();
|
||||||
|
if let Some(Err(err)) = r {
|
||||||
|
self.sender
|
||||||
|
.send(ThreadEvent::UIEvent(UIEvent::Notification(
|
||||||
|
Some("Could not send message".to_string()),
|
||||||
|
err.to_string(),
|
||||||
|
Some(crate::types::NotificationType::ERROR),
|
||||||
|
)))
|
||||||
|
.expect("Could not send event on main channel");
|
||||||
|
}
|
||||||
|
self.sender
|
||||||
|
.send(ThreadEvent::UIEvent(UIEvent::StatusEvent(
|
||||||
|
StatusEvent::JobFinished(*job_id),
|
||||||
|
)))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
JobRequest::CopyTo(mailbox_hash, _, mut chan) => {
|
JobRequest::CopyTo(mailbox_hash, _, mut chan) => {
|
||||||
if let Err(err) = chan
|
if let Err(err) = chan
|
||||||
.try_recv()
|
.try_recv()
|
||||||
|
|
|
@ -28,8 +28,7 @@ use std::collections::HashMap;
|
||||||
pub struct ComposingSettings {
|
pub struct ComposingSettings {
|
||||||
/// A command to pipe new emails to
|
/// A command to pipe new emails to
|
||||||
/// Required
|
/// Required
|
||||||
#[serde(alias = "mailer-command", alias = "mailer-cmd", alias = "mailer_cmd")]
|
pub send_mail: SendMail,
|
||||||
pub mailer_command: String,
|
|
||||||
/// Command to launch editor. Can have arguments. Draft filename is given as the last argument. If it's missing, the environment variable $EDITOR is looked up.
|
/// Command to launch editor. Can have arguments. Draft filename is given as the last argument. If it's missing, the environment variable $EDITOR is looked up.
|
||||||
#[serde(
|
#[serde(
|
||||||
default = "none",
|
default = "none",
|
||||||
|
@ -54,7 +53,7 @@ pub struct ComposingSettings {
|
||||||
impl Default for ComposingSettings {
|
impl Default for ComposingSettings {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
ComposingSettings {
|
ComposingSettings {
|
||||||
mailer_command: String::new(),
|
send_mail: SendMail::ShellCommand("/bin/false".into()),
|
||||||
editor_command: None,
|
editor_command: None,
|
||||||
embed: false,
|
embed: false,
|
||||||
format_flowed: true,
|
format_flowed: true,
|
||||||
|
@ -62,3 +61,11 @@ impl Default for ComposingSettings {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum SendMail {
|
||||||
|
#[cfg(feature = "smtp")]
|
||||||
|
Smtp(melib::smtp::SmtpServerConf),
|
||||||
|
ShellCommand(String),
|
||||||
|
}
|
||||||
|
|
|
@ -202,9 +202,8 @@ impl Default for ShortcutsOverride {
|
||||||
pub struct ComposingSettingsOverride {
|
pub struct ComposingSettingsOverride {
|
||||||
#[doc = " A command to pipe new emails to"]
|
#[doc = " A command to pipe new emails to"]
|
||||||
#[doc = " Required"]
|
#[doc = " Required"]
|
||||||
#[serde(alias = "mailer-command", alias = "mailer-cmd", alias = "mailer_cmd")]
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub mailer_command: Option<String>,
|
pub send_mail: Option<SendMail>,
|
||||||
#[doc = " Command to launch editor. Can have arguments. Draft filename is given as the last argument. If it's missing, the environment variable $EDITOR is looked up."]
|
#[doc = " Command to launch editor. Can have arguments. Draft filename is given as the last argument. If it's missing, the environment variable $EDITOR is looked up."]
|
||||||
#[serde(alias = "editor-command", alias = "editor-cmd", alias = "editor_cmd")]
|
#[serde(alias = "editor-command", alias = "editor-cmd", alias = "editor_cmd")]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
@ -226,7 +225,7 @@ pub struct ComposingSettingsOverride {
|
||||||
impl Default for ComposingSettingsOverride {
|
impl Default for ComposingSettingsOverride {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
ComposingSettingsOverride {
|
ComposingSettingsOverride {
|
||||||
mailer_command: None,
|
send_mail: None,
|
||||||
editor_command: None,
|
editor_command: None,
|
||||||
embed: None,
|
embed: None,
|
||||||
format_flowed: None,
|
format_flowed: None,
|
||||||
|
|
|
@ -77,7 +77,7 @@ macro_rules! uuid_hash_type {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl $n {
|
impl $n {
|
||||||
fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
$n(Uuid::new_v4())
|
$n(Uuid::new_v4())
|
||||||
}
|
}
|
||||||
pub fn null() -> Self {
|
pub fn null() -> Self {
|
||||||
|
@ -216,6 +216,8 @@ impl JobExecutor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type JobChannel<T> = oneshot::Receiver<Result<T>>;
|
||||||
|
|
||||||
///// Spawns a future on the executor.
|
///// Spawns a future on the executor.
|
||||||
//fn spawn<F, R>(future: F) -> JoinHandle<R>
|
//fn spawn<F, R>(future: F) -> JoinHandle<R>
|
||||||
//where
|
//where
|
||||||
|
|
|
@ -38,6 +38,7 @@ pub use self::helpers::*;
|
||||||
use super::execute::Action;
|
use super::execute::Action;
|
||||||
use super::jobs::JobId;
|
use super::jobs::JobId;
|
||||||
use super::terminal::*;
|
use super::terminal::*;
|
||||||
|
use crate::components::{Component, ComponentId};
|
||||||
|
|
||||||
use melib::backends::{AccountHash, MailboxHash};
|
use melib::backends::{AccountHash, MailboxHash};
|
||||||
use melib::{EnvelopeHash, RefreshEvent, ThreadHash};
|
use melib::{EnvelopeHash, RefreshEvent, ThreadHash};
|
||||||
|
@ -125,8 +126,8 @@ pub enum UIEvent {
|
||||||
EnvelopeRemove(EnvelopeHash, ThreadHash),
|
EnvelopeRemove(EnvelopeHash, ThreadHash),
|
||||||
Contacts(ContactEvent),
|
Contacts(ContactEvent),
|
||||||
Compose(ComposeEvent),
|
Compose(ComposeEvent),
|
||||||
FinishedUIDialog(crate::components::ComponentId, UIMessage),
|
FinishedUIDialog(ComponentId, UIMessage),
|
||||||
GlobalUIDialog(Box<dyn crate::components::Component>),
|
GlobalUIDialog(Box<dyn Component>),
|
||||||
Timer(u8),
|
Timer(u8),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue