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", ]
|
||||
|
||||
[features]
|
||||
default = ["sqlite3", "notmuch", "regexp"]
|
||||
default = ["sqlite3", "notmuch", "regexp", "smtp"]
|
||||
notmuch = ["melib/notmuch_backend", ]
|
||||
jmap = ["melib/jmap_backend",]
|
||||
sqlite3 = ["melib/sqlite3"]
|
||||
smtp = ["melib/smtp"]
|
||||
regexp = ["pcre2"]
|
||||
cli-docs = []
|
||||
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| {
|
||||
let mut b = f.lock().unwrap();
|
||||
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(level.to_string().as_bytes()).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.flush().unwrap();
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ use melib::list_management;
|
|||
use melib::Draft;
|
||||
|
||||
use crate::conf::accounts::JobRequest;
|
||||
use crate::jobs::{oneshot, JobId};
|
||||
use crate::jobs::{JobChannel, JobId, JoinHandle};
|
||||
use crate::terminal::embed::EmbedGrid;
|
||||
use nix::sys::wait::WaitStatus;
|
||||
use std::str::FromStr;
|
||||
|
@ -66,7 +66,7 @@ impl std::ops::DerefMut for EmbedStatus {
|
|||
#[derive(Debug)]
|
||||
pub struct Composer {
|
||||
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,
|
||||
|
||||
cursor: Cursor,
|
||||
|
@ -120,6 +120,7 @@ enum ViewMode {
|
|||
Embed,
|
||||
SelectRecipients(UIDialog<Address>),
|
||||
Send(UIConfirmationDialog),
|
||||
WaitingForSendResult(UIDialog<char>, JoinHandle, JobId, JobChannel<()>),
|
||||
}
|
||||
|
||||
impl ViewMode {
|
||||
|
@ -612,6 +613,10 @@ impl Component for Composer {
|
|||
/* Let user choose whether to quit with/without saving or cancel */
|
||||
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.draw_attachments(grid, attachment_area, context);
|
||||
|
@ -673,28 +678,61 @@ impl Component for Composer {
|
|||
{
|
||||
if let Some(true) = result.downcast_ref::<bool>() {
|
||||
self.update_draft();
|
||||
if send_draft(
|
||||
match send_draft(
|
||||
self.sign_mail,
|
||||
context,
|
||||
self.account_cursor,
|
||||
self.draft.clone(),
|
||||
SpecialUsageMailbox::Sent,
|
||||
Flag::SEEN,
|
||||
false,
|
||||
) {
|
||||
context
|
||||
.replies
|
||||
.push_back(UIEvent::Action(Tab(Kill(self.id))));
|
||||
} else {
|
||||
save_draft(
|
||||
self.draft.clone().finalise().unwrap().as_bytes(),
|
||||
context,
|
||||
SpecialUsageMailbox::Drafts,
|
||||
Flag::SEEN | Flag::DRAFT,
|
||||
self.account_cursor,
|
||||
);
|
||||
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
|
||||
.replies
|
||||
.push_back(UIEvent::Action(Tab(Kill(self.id))));
|
||||
}
|
||||
Err(err) => {
|
||||
context.replies.push_back(UIEvent::Notification(
|
||||
None,
|
||||
err.to_string(),
|
||||
Some(NotificationType::ERROR),
|
||||
));
|
||||
save_draft(
|
||||
self.draft.clone().finalise().unwrap().as_bytes(),
|
||||
context,
|
||||
SpecialUsageMailbox::Drafts,
|
||||
Flag::SEEN | Flag::DRAFT,
|
||||
self.account_cursor,
|
||||
);
|
||||
self.mode = ViewMode::Edit;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.mode = ViewMode::Edit;
|
||||
self.set_dirty(true);
|
||||
return true;
|
||||
}
|
||||
(ViewMode::Send(ref mut selector), _) => {
|
||||
|
@ -753,6 +791,59 @@ impl Component for Composer {
|
|||
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
|
||||
|
@ -1279,76 +1370,58 @@ pub fn send_draft(
|
|||
mut draft: Draft,
|
||||
mailbox_type: SpecialUsageMailbox,
|
||||
flags: Flag,
|
||||
) -> bool {
|
||||
use std::io::Write;
|
||||
use std::process::{Command, Stdio};
|
||||
complete_in_background: bool,
|
||||
) -> Result<Option<(JobId, JoinHandle, JobChannel<()>)>> {
|
||||
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() {
|
||||
let mut content_type = ContentType::default();
|
||||
if format_flowed {
|
||||
if let ContentType::Text {
|
||||
ref mut parameters, ..
|
||||
} = content_type
|
||||
{
|
||||
parameters.push((b"format".to_vec(), b"flowed".to_vec()));
|
||||
}
|
||||
if sign_mail.is_true() {
|
||||
let mut content_type = ContentType::default();
|
||||
if format_flowed {
|
||||
if let ContentType::Text {
|
||||
ref mut parameters, ..
|
||||
} = content_type
|
||||
{
|
||||
parameters.push((b"format".to_vec(), b"flowed".to_vec()));
|
||||
}
|
||||
}
|
||||
|
||||
let mut body: AttachmentBuilder = Attachment::new(
|
||||
content_type,
|
||||
let mut body: AttachmentBuilder = Attachment::new(
|
||||
content_type,
|
||||
Default::default(),
|
||||
std::mem::replace(&mut draft.body, String::new()).into_bytes(),
|
||||
)
|
||||
.into();
|
||||
if !draft.attachments.is_empty() {
|
||||
let mut parts = std::mem::replace(&mut draft.attachments, Vec::new());
|
||||
parts.insert(0, body);
|
||||
let boundary = ContentType::make_boundary(&parts);
|
||||
body = Attachment::new(
|
||||
ContentType::Multipart {
|
||||
boundary: boundary.into_bytes(),
|
||||
kind: MultipartType::Mixed,
|
||||
parts: parts.into_iter().map(|a| a.into()).collect::<Vec<_>>(),
|
||||
},
|
||||
Default::default(),
|
||||
std::mem::replace(&mut draft.body, String::new()).into_bytes(),
|
||||
Vec::new(),
|
||||
)
|
||||
.into();
|
||||
if !draft.attachments.is_empty() {
|
||||
let mut parts = std::mem::replace(&mut draft.attachments, Vec::new());
|
||||
parts.insert(0, body);
|
||||
let boundary = ContentType::make_boundary(&parts);
|
||||
body = Attachment::new(
|
||||
ContentType::Multipart {
|
||||
boundary: boundary.into_bytes(),
|
||||
kind: MultipartType::Mixed,
|
||||
parts: parts.into_iter().map(|a| a.into()).collect::<Vec<_>>(),
|
||||
},
|
||||
Default::default(),
|
||||
Vec::new(),
|
||||
)
|
||||
.into();
|
||||
}
|
||||
let output = crate::components::mail::pgp::sign(
|
||||
body.into(),
|
||||
mailbox_acc_settings!(context[account_cursor].pgp.gpg_binary)
|
||||
.as_ref()
|
||||
.map(|s| s.as_str()),
|
||||
mailbox_acc_settings!(context[account_cursor].pgp.key)
|
||||
.as_ref()
|
||||
.map(|s| s.as_str()),
|
||||
);
|
||||
if let Err(e) = &output {
|
||||
debug!("{:?} could not sign draft msg", e);
|
||||
}
|
||||
let output = crate::components::mail::pgp::sign(
|
||||
body.into(),
|
||||
mailbox_acc_settings!(context[account_cursor].pgp.gpg_binary)
|
||||
.as_ref()
|
||||
.map(|s| s.as_str()),
|
||||
mailbox_acc_settings!(context[account_cursor].pgp.key)
|
||||
.as_ref()
|
||||
.map(|s| s.as_str()),
|
||||
);
|
||||
match output {
|
||||
Err(err) => {
|
||||
debug!("{:?} could not sign draft msg", err);
|
||||
log(
|
||||
format!(
|
||||
"Could not sign draft in account `{}`: {}.",
|
||||
context.accounts[account_cursor].name(),
|
||||
e.to_string()
|
||||
err.to_string()
|
||||
),
|
||||
ERROR,
|
||||
);
|
||||
|
@ -1357,62 +1430,38 @@ pub fn send_draft(
|
|||
"Could not sign draft in account `{}`.",
|
||||
context.accounts[account_cursor].name()
|
||||
)),
|
||||
e.to_string(),
|
||||
err.to_string(),
|
||||
Some(NotificationType::ERROR),
|
||||
));
|
||||
return false;
|
||||
return Err(err);
|
||||
}
|
||||
draft.attachments.push(output.unwrap());
|
||||
} else {
|
||||
let mut content_type = ContentType::default();
|
||||
if format_flowed {
|
||||
if let ContentType::Text {
|
||||
ref mut parameters, ..
|
||||
} = content_type
|
||||
{
|
||||
parameters.push((b"format".to_vec(), b"flowed".to_vec()));
|
||||
}
|
||||
|
||||
let body: AttachmentBuilder = Attachment::new(
|
||||
content_type,
|
||||
Default::default(),
|
||||
std::mem::replace(&mut draft.body, String::new()).into_bytes(),
|
||||
)
|
||||
.into();
|
||||
draft.attachments.insert(0, body);
|
||||
Ok(output) => {
|
||||
draft.attachments.push(output);
|
||||
}
|
||||
}
|
||||
bytes = draft.finalise().unwrap();
|
||||
stdin
|
||||
.write_all(bytes.as_bytes())
|
||||
.expect("Failed to write to stdin");
|
||||
}
|
||||
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
|
||||
let mut content_type = ContentType::default();
|
||||
if format_flowed {
|
||||
if let ContentType::Text {
|
||||
ref mut parameters, ..
|
||||
} = content_type
|
||||
{
|
||||
parameters.push((b"format".to_vec(), b"flowed".to_vec()));
|
||||
}
|
||||
|
||||
let body: AttachmentBuilder = Attachment::new(
|
||||
content_type,
|
||||
Default::default(),
|
||||
std::mem::replace(&mut draft.body, String::new()).into_bytes(),
|
||||
)
|
||||
} 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);
|
||||
.into();
|
||||
draft.attachments.insert(0, body);
|
||||
}
|
||||
}
|
||||
let bytes = draft.finalise().unwrap();
|
||||
let send_mail = mailbox_acc_settings!(context[account_cursor].composing.send_mail).clone();
|
||||
let ret =
|
||||
context.accounts[account_cursor].send(bytes.clone(), send_mail, complete_in_background);
|
||||
save_draft(
|
||||
bytes.as_bytes(),
|
||||
context,
|
||||
|
@ -1420,7 +1469,7 @@ pub fn send_draft(
|
|||
flags,
|
||||
account_cursor,
|
||||
);
|
||||
true
|
||||
ret
|
||||
}
|
||||
|
||||
pub fn save_draft(
|
||||
|
|
|
@ -1495,14 +1495,23 @@ impl Component for MailView {
|
|||
* on its own */
|
||||
drop(detect);
|
||||
drop(envelope);
|
||||
return super::compose::send_draft(
|
||||
if let Err(err) = super::compose::send_draft(
|
||||
ToggleFlag::False,
|
||||
context,
|
||||
self.coordinates.0,
|
||||
draft,
|
||||
SpecialUsageMailbox::Sent,
|
||||
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) => {
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
*/
|
||||
|
||||
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::backends::{
|
||||
AccountHash, BackendOp, Backends, MailBackend, Mailbox, MailboxHash, NotifyFn, ReadOnlyOp,
|
||||
|
@ -173,7 +173,8 @@ pub enum JobRequest {
|
|||
Refresh(MailboxHash, JoinHandle, oneshot::Receiver<Result<()>>),
|
||||
SetFlags(EnvelopeHash, 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>>>),
|
||||
DeleteMessage(EnvelopeHash, JoinHandle, oneshot::Receiver<Result<()>>),
|
||||
CreateMailbox(
|
||||
|
@ -215,7 +216,10 @@ impl core::fmt::Debug for JobRequest {
|
|||
write!(f, "JobRequest::SetMailboxSubscription")
|
||||
}
|
||||
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(())
|
||||
}
|
||||
|
||||
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(
|
||||
&mut self,
|
||||
env_hash: EnvelopeHash,
|
||||
|
@ -1678,6 +1757,30 @@ impl Account {
|
|||
.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) => {
|
||||
if let Err(err) = chan
|
||||
.try_recv()
|
||||
|
|
|
@ -28,8 +28,7 @@ use std::collections::HashMap;
|
|||
pub struct ComposingSettings {
|
||||
/// A command to pipe new emails to
|
||||
/// Required
|
||||
#[serde(alias = "mailer-command", alias = "mailer-cmd", alias = "mailer_cmd")]
|
||||
pub mailer_command: String,
|
||||
pub send_mail: SendMail,
|
||||
/// 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(
|
||||
default = "none",
|
||||
|
@ -54,7 +53,7 @@ pub struct ComposingSettings {
|
|||
impl Default for ComposingSettings {
|
||||
fn default() -> Self {
|
||||
ComposingSettings {
|
||||
mailer_command: String::new(),
|
||||
send_mail: SendMail::ShellCommand("/bin/false".into()),
|
||||
editor_command: None,
|
||||
embed: false,
|
||||
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 {
|
||||
#[doc = " A command to pipe new emails to"]
|
||||
#[doc = " Required"]
|
||||
#[serde(alias = "mailer-command", alias = "mailer-cmd", alias = "mailer_cmd")]
|
||||
#[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."]
|
||||
#[serde(alias = "editor-command", alias = "editor-cmd", alias = "editor_cmd")]
|
||||
#[serde(default)]
|
||||
|
@ -226,7 +225,7 @@ pub struct ComposingSettingsOverride {
|
|||
impl Default for ComposingSettingsOverride {
|
||||
fn default() -> Self {
|
||||
ComposingSettingsOverride {
|
||||
mailer_command: None,
|
||||
send_mail: None,
|
||||
editor_command: None,
|
||||
embed: None,
|
||||
format_flowed: None,
|
||||
|
|
|
@ -77,7 +77,7 @@ macro_rules! uuid_hash_type {
|
|||
}
|
||||
|
||||
impl $n {
|
||||
fn new() -> Self {
|
||||
pub fn new() -> Self {
|
||||
$n(Uuid::new_v4())
|
||||
}
|
||||
pub fn null() -> Self {
|
||||
|
@ -216,6 +216,8 @@ impl JobExecutor {
|
|||
}
|
||||
}
|
||||
|
||||
pub type JobChannel<T> = oneshot::Receiver<Result<T>>;
|
||||
|
||||
///// Spawns a future on the executor.
|
||||
//fn spawn<F, R>(future: F) -> JoinHandle<R>
|
||||
//where
|
||||
|
|
|
@ -38,6 +38,7 @@ pub use self::helpers::*;
|
|||
use super::execute::Action;
|
||||
use super::jobs::JobId;
|
||||
use super::terminal::*;
|
||||
use crate::components::{Component, ComponentId};
|
||||
|
||||
use melib::backends::{AccountHash, MailboxHash};
|
||||
use melib::{EnvelopeHash, RefreshEvent, ThreadHash};
|
||||
|
@ -125,8 +126,8 @@ pub enum UIEvent {
|
|||
EnvelopeRemove(EnvelopeHash, ThreadHash),
|
||||
Contacts(ContactEvent),
|
||||
Compose(ComposeEvent),
|
||||
FinishedUIDialog(crate::components::ComponentId, UIMessage),
|
||||
GlobalUIDialog(Box<dyn crate::components::Component>),
|
||||
FinishedUIDialog(ComponentId, UIMessage),
|
||||
GlobalUIDialog(Box<dyn Component>),
|
||||
Timer(u8),
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue