save msg upon error (#59)

This commit is contained in:
Clément DOUIN 2021-04-15 23:29:58 +02:00
parent f4cc584716
commit 33185dba86
No known key found for this signature in database
GPG key ID: 69C9B9CFFDEE2DEF
3 changed files with 176 additions and 64 deletions

View file

@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
### Fixed
- Save msg upon error [#59]
### Changed ### Changed
- SMTP timeout [#87] - SMTP timeout [#87]
@ -151,6 +155,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[#40]: https://github.com/soywod/himalaya/issues/40 [#40]: https://github.com/soywod/himalaya/issues/40
[#41]: https://github.com/soywod/himalaya/issues/41 [#41]: https://github.com/soywod/himalaya/issues/41
[#58]: https://github.com/soywod/himalaya/issues/58 [#58]: https://github.com/soywod/himalaya/issues/58
[#59]: https://github.com/soywod/himalaya/issues/59
[#61]: https://github.com/soywod/himalaya/issues/61 [#61]: https://github.com/soywod/himalaya/issues/61
[#71]: https://github.com/soywod/himalaya/issues/71 [#71]: https://github.com/soywod/himalaya/issues/71
[#74]: https://github.com/soywod/himalaya/issues/74 [#74]: https://github.com/soywod/himalaya/issues/74

View file

@ -1,80 +1,167 @@
use error_chain::error_chain; use error_chain::error_chain;
use log::{debug, error, trace};
use std::{ use std::{
env, env,
fs::File, fs::{self, File},
io::{self, Read, Write}, io::{self, Read, Write},
path::PathBuf,
process::Command, process::Command,
}; };
error_chain! {} error_chain! {
foreign_links {
Utf8(std::string::FromUtf8Error);
}
}
fn draft_path() -> PathBuf {
env::temp_dir().join("himalaya-draft.mail")
}
pub fn remove_draft() -> Result<()> {
debug!("[input] remove draft");
let draft_path = draft_path();
debug!("[input] draft path: {:?}", draft_path);
fs::remove_file(&draft_path)
.chain_err(|| format!("Could not delete draft file {:?}", draft_path))
}
pub fn open_editor_with_tpl(tpl: &[u8]) -> Result<String> { pub fn open_editor_with_tpl(tpl: &[u8]) -> Result<String> {
// Creates draft file debug!("[input] open editor with tpl");
let mut draft_path = env::temp_dir(); trace!("{}", String::from_utf8(tpl.to_vec())?);
draft_path.push("himalaya-draft.mail");
File::create(&draft_path)
.chain_err(|| format!("Cannot create file `{}`", draft_path.to_string_lossy()))?
.write(tpl)
.chain_err(|| format!("Cannot write file `{}`", draft_path.to_string_lossy()))?;
// Opens editor and saves user input to draft file let draft_path = draft_path();
Command::new(env::var("EDITOR").chain_err(|| "Cannot find `EDITOR` env var")?) debug!("[input] draft path: {:?}", draft_path);
if draft_path.exists() {
debug!("[input] draft found");
loop {
match pre_edit_choice() {
Ok(choice) => match choice {
PreEditChoice::Edit => return open_editor_with_draft(),
PreEditChoice::Discard => break,
PreEditChoice::Quit => return Err("Edition aborted".into()),
},
Err(err) => error!("{}", err),
}
}
}
debug!("[input] create draft");
File::create(&draft_path)
.chain_err(|| format!("Could not create draft file {:?}", draft_path))?
.write(tpl)
.chain_err(|| format!("Could not write draft file {:?}", draft_path))?;
debug!("[input] open editor");
Command::new(env::var("EDITOR").chain_err(|| "Could not find `EDITOR` env var")?)
.arg(&draft_path) .arg(&draft_path)
.status() .status()
.chain_err(|| "Cannot start editor")?; .chain_err(|| "Could not launch editor")?;
// Extracts draft file content debug!("[input] read draft");
let mut draft = String::new(); let mut draft = String::new();
File::open(&draft_path) File::open(&draft_path)
.chain_err(|| format!("Cannot open file `{}`", draft_path.to_string_lossy()))? .chain_err(|| format!("Could not open draft file {:?}", draft_path))?
.read_to_string(&mut draft) .read_to_string(&mut draft)
.chain_err(|| format!("Cannot read file `{}`", draft_path.to_string_lossy()))?; .chain_err(|| format!("Could not read draft file {:?}", draft_path))?;
Ok(draft) Ok(draft)
} }
pub fn open_editor_with_draft() -> Result<String> { pub fn open_editor_with_draft() -> Result<String> {
// Creates draft file debug!("[input] open editor with draft");
let mut draft_path = env::temp_dir();
draft_path.push("himalaya-draft.mail"); let draft_path = draft_path();
debug!("[input] draft path: {:?}", draft_path);
// Opens editor and saves user input to draft file // Opens editor and saves user input to draft file
Command::new(env::var("EDITOR").chain_err(|| "Cannot find `EDITOR` env var")?) Command::new(env::var("EDITOR").chain_err(|| "Could not find `EDITOR` env var")?)
.arg(&draft_path) .arg(&draft_path)
.status() .status()
.chain_err(|| "Cannot start editor")?; .chain_err(|| "Could not launch editor")?;
// Extracts draft file content // Extracts draft file content
let mut draft = String::new(); let mut draft = String::new();
File::open(&draft_path) File::open(&draft_path)
.chain_err(|| format!("Cannot open file `{}`", draft_path.to_string_lossy()))? .chain_err(|| format!("Could not open file {:?}", draft_path))?
.read_to_string(&mut draft) .read_to_string(&mut draft)
.chain_err(|| format!("Cannot read file `{}`", draft_path.to_string_lossy()))?; .chain_err(|| format!("Could not read file {:?}", draft_path))?;
Ok(draft) Ok(draft)
} }
pub enum Choice { pub enum PreEditChoice {
Send,
Draft,
Edit, Edit,
Discard,
Quit, Quit,
} }
pub fn post_edit_choice() -> Result<Choice> { pub fn pre_edit_choice() -> Result<PreEditChoice> {
print!("(s)end, (d)raft, (e)dit or (q)uit? "); debug!("[input] pre edit choice");
io::stdout().flush().chain_err(|| "Cannot flush stdout")?;
println!("A draft was found:");
print!("(e)dit, (d)iscard or (q)uit? ");
io::stdout()
.flush()
.chain_err(|| "Could not flush stdout")?;
let mut buf = String::new(); let mut buf = String::new();
io::stdin() io::stdin()
.read_line(&mut buf) .read_line(&mut buf)
.chain_err(|| "Cannot read stdin")?; .chain_err(|| "Could not read stdin")?;
match buf.bytes().next().map(|bytes| bytes as char) { match buf.bytes().next().map(|bytes| bytes as char) {
Some('s') => Ok(Choice::Send), Some('e') => {
Some('d') => Ok(Choice::Draft), debug!("[input] pre edit choice: edit matched");
Some('e') => Ok(Choice::Edit), Ok(PreEditChoice::Edit)
Some('q') => Ok(Choice::Quit), }
Some('d') => {
debug!("[input] pre edit choice: discard matched");
Ok(PreEditChoice::Discard)
}
Some('q') => {
debug!("[input] pre edit choice: quit matched");
Ok(PreEditChoice::Quit)
}
Some(choice) => {
debug!("[input] pre edit choice: invalid choice {}", choice);
Err(format!("Invalid choice `{}`", choice).into())
}
None => {
debug!("[input] pre edit choice: empty choice");
Err("Empty choice".into())
}
}
}
pub enum PostEditChoice {
Send,
Edit,
LocalDraft,
RemoteDraft,
Discard,
}
pub fn post_edit_choice() -> Result<PostEditChoice> {
print!("(s)end, (e)dit, (l)ocal/(r)emote draft or (d)iscard? ");
io::stdout()
.flush()
.chain_err(|| "Could not flush stdout")?;
let mut buf = String::new();
io::stdin()
.read_line(&mut buf)
.chain_err(|| "Could not read stdin")?;
match buf.bytes().next().map(|bytes| bytes as char) {
Some('s') => Ok(PostEditChoice::Send),
Some('l') => Ok(PostEditChoice::LocalDraft),
Some('r') => Ok(PostEditChoice::RemoteDraft),
Some('e') => Ok(PostEditChoice::Edit),
Some('d') => Ok(PostEditChoice::Discard),
Some(choice) => Err(format!("Invalid choice `{}`", choice).into()), Some(choice) => Err(format!("Invalid choice `{}`", choice).into()),
None => Err("Empty choice".into()), None => Err("Empty choice".into()),
} }

View file

@ -302,25 +302,31 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> {
loop { loop {
match input::post_edit_choice() { match input::post_edit_choice() {
Ok(choice) => match choice { Ok(choice) => match choice {
input::Choice::Send => { input::PostEditChoice::Send => {
debug!("Sending message…"); debug!("Sending message…");
let msg = msg.to_sendable_msg()?; let msg = msg.to_sendable_msg()?;
smtp::send(&account, &msg)?; smtp::send(&account, &msg)?;
imap_conn.append_msg("Sent", &msg.formatted(), &[Flag::Seen])?; imap_conn.append_msg("Sent", &msg.formatted(), &[Flag::Seen])?;
input::remove_draft()?;
info!("Message successfully sent"); info!("Message successfully sent");
break; break;
} }
input::Choice::Draft => { input::PostEditChoice::Edit => {
debug!("Saving to draft…");
imap_conn.append_msg("Drafts", &msg.to_vec()?, &[Flag::Seen])?;
info!("Message successfully saved to Drafts");
break;
}
input::Choice::Edit => {
let content = input::open_editor_with_draft()?; let content = input::open_editor_with_draft()?;
msg = Msg::from(content); msg = Msg::from(content);
} }
input::Choice::Quit => break, input::PostEditChoice::LocalDraft => break,
input::PostEditChoice::RemoteDraft => {
debug!("Saving to draft…");
imap_conn.append_msg("Drafts", &msg.to_vec()?, &[Flag::Seen])?;
input::remove_draft()?;
info!("Message successfully saved to Drafts");
break;
}
input::PostEditChoice::Discard => {
input::remove_draft()?;
break;
}
}, },
Err(err) => error!("{}", err), Err(err) => error!("{}", err),
} }
@ -400,25 +406,32 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> {
loop { loop {
match input::post_edit_choice() { match input::post_edit_choice() {
Ok(choice) => match choice { Ok(choice) => match choice {
input::Choice::Send => { input::PostEditChoice::Send => {
debug!("Sending message…"); debug!("Sending message…");
smtp::send(&account, &msg.to_sendable_msg()?)?; let msg = msg.to_sendable_msg()?;
imap_conn.append_msg("Sent", &msg.to_vec()?, &[Flag::Seen])?; smtp::send(&account, &msg)?;
imap_conn.append_msg("Sent", &msg.formatted(), &[Flag::Seen])?;
imap_conn.add_flags(mbox, uid, "\\Answered")?; imap_conn.add_flags(mbox, uid, "\\Answered")?;
input::remove_draft()?;
info!("Message successfully sent"); info!("Message successfully sent");
break; break;
} }
input::Choice::Draft => { input::PostEditChoice::Edit => {
debug!("Saving draft message…");
imap_conn.append_msg("Drafts", &msg.to_vec()?, &[Flag::Seen])?;
info!("Message successfully saved to Drafts");
break;
}
input::Choice::Edit => {
let content = input::open_editor_with_draft()?; let content = input::open_editor_with_draft()?;
msg = Msg::from(content); msg = Msg::from(content);
} }
input::Choice::Quit => break, input::PostEditChoice::LocalDraft => break,
input::PostEditChoice::RemoteDraft => {
debug!("Saving to draft…");
imap_conn.append_msg("Drafts", &msg.to_vec()?, &[Flag::Seen])?;
input::remove_draft()?;
info!("Message successfully saved to Drafts");
break;
}
input::PostEditChoice::Discard => {
input::remove_draft()?;
break;
}
}, },
Err(err) => error!("{}", err), Err(err) => error!("{}", err),
} }
@ -449,24 +462,31 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> {
loop { loop {
match input::post_edit_choice() { match input::post_edit_choice() {
Ok(choice) => match choice { Ok(choice) => match choice {
input::Choice::Send => { input::PostEditChoice::Send => {
debug!("Sending message…"); debug!("Sending message…");
smtp::send(&account, &msg.to_sendable_msg()?)?; let msg = msg.to_sendable_msg()?;
imap_conn.append_msg("Sent", &msg.to_vec()?, &[Flag::Seen])?; smtp::send(&account, &msg)?;
imap_conn.append_msg("Sent", &msg.formatted(), &[Flag::Seen])?;
input::remove_draft()?;
info!("Message successfully sent"); info!("Message successfully sent");
break; break;
} }
input::Choice::Draft => { input::PostEditChoice::Edit => {
debug!("Saving draft message…");
imap_conn.append_msg("Drafts", &msg.to_vec()?, &[Flag::Seen])?;
info!("Message successfully saved to Drafts");
break;
}
input::Choice::Edit => {
let content = input::open_editor_with_draft()?; let content = input::open_editor_with_draft()?;
msg = Msg::from(content); msg = Msg::from(content);
} }
input::Choice::Quit => break, input::PostEditChoice::LocalDraft => break,
input::PostEditChoice::RemoteDraft => {
debug!("Saving to draft…");
imap_conn.append_msg("Drafts", &msg.to_vec()?, &[Flag::Seen])?;
input::remove_draft()?;
info!("Message successfully saved to Drafts");
break;
}
input::PostEditChoice::Discard => {
input::remove_draft()?;
break;
}
}, },
Err(err) => error!("{}", err), Err(err) => error!("{}", err),
} }