diff --git a/CHANGELOG.md b/CHANGELOG.md index c2adf37..d3b4471 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- Save msg upon error [#59] + ### Changed - 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 [#41]: https://github.com/soywod/himalaya/issues/41 [#58]: https://github.com/soywod/himalaya/issues/58 +[#59]: https://github.com/soywod/himalaya/issues/59 [#61]: https://github.com/soywod/himalaya/issues/61 [#71]: https://github.com/soywod/himalaya/issues/71 [#74]: https://github.com/soywod/himalaya/issues/74 diff --git a/src/input.rs b/src/input.rs index 22e0655..9ddbc04 100644 --- a/src/input.rs +++ b/src/input.rs @@ -1,80 +1,167 @@ use error_chain::error_chain; +use log::{debug, error, trace}; use std::{ env, - fs::File, + fs::{self, File}, io::{self, Read, Write}, + path::PathBuf, 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 { - // Creates draft file - let mut draft_path = env::temp_dir(); - 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()))?; + debug!("[input] open editor with tpl"); + trace!("{}", String::from_utf8(tpl.to_vec())?); - // Opens editor and saves user input to draft file - Command::new(env::var("EDITOR").chain_err(|| "Cannot find `EDITOR` env var")?) + let draft_path = draft_path(); + 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) .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(); 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) - .chain_err(|| format!("Cannot read file `{}`", draft_path.to_string_lossy()))?; + .chain_err(|| format!("Could not read draft file {:?}", draft_path))?; Ok(draft) } pub fn open_editor_with_draft() -> Result { - // Creates draft file - let mut draft_path = env::temp_dir(); - draft_path.push("himalaya-draft.mail"); + debug!("[input] open editor with draft"); + + let draft_path = draft_path(); + debug!("[input] draft path: {:?}", draft_path); // 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) .status() - .chain_err(|| "Cannot start editor")?; + .chain_err(|| "Could not launch editor")?; // Extracts draft file content let mut draft = String::new(); 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) - .chain_err(|| format!("Cannot read file `{}`", draft_path.to_string_lossy()))?; + .chain_err(|| format!("Could not read file {:?}", draft_path))?; Ok(draft) } -pub enum Choice { - Send, - Draft, +pub enum PreEditChoice { Edit, + Discard, Quit, } -pub fn post_edit_choice() -> Result { - print!("(s)end, (d)raft, (e)dit or (q)uit? "); - io::stdout().flush().chain_err(|| "Cannot flush stdout")?; +pub fn pre_edit_choice() -> Result { + debug!("[input] pre edit choice"); + + 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(); io::stdin() .read_line(&mut buf) - .chain_err(|| "Cannot read stdin")?; + .chain_err(|| "Could not read stdin")?; match buf.bytes().next().map(|bytes| bytes as char) { - Some('s') => Ok(Choice::Send), - Some('d') => Ok(Choice::Draft), - Some('e') => Ok(Choice::Edit), - Some('q') => Ok(Choice::Quit), + Some('e') => { + debug!("[input] pre edit choice: edit matched"); + Ok(PreEditChoice::Edit) + } + 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 { + 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()), None => Err("Empty choice".into()), } diff --git a/src/msg/cli.rs b/src/msg/cli.rs index d4f6d70..5476a11 100644 --- a/src/msg/cli.rs +++ b/src/msg/cli.rs @@ -302,25 +302,31 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> { loop { match input::post_edit_choice() { Ok(choice) => match choice { - input::Choice::Send => { + input::PostEditChoice::Send => { debug!("Sending message…"); let msg = msg.to_sendable_msg()?; smtp::send(&account, &msg)?; imap_conn.append_msg("Sent", &msg.formatted(), &[Flag::Seen])?; + input::remove_draft()?; info!("Message successfully sent"); break; } - input::Choice::Draft => { - debug!("Saving to draft…"); - imap_conn.append_msg("Drafts", &msg.to_vec()?, &[Flag::Seen])?; - info!("Message successfully saved to Drafts"); - break; - } - input::Choice::Edit => { + input::PostEditChoice::Edit => { let content = input::open_editor_with_draft()?; 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), } @@ -400,25 +406,32 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> { loop { match input::post_edit_choice() { Ok(choice) => match choice { - input::Choice::Send => { + input::PostEditChoice::Send => { debug!("Sending message…"); - smtp::send(&account, &msg.to_sendable_msg()?)?; - imap_conn.append_msg("Sent", &msg.to_vec()?, &[Flag::Seen])?; + let msg = msg.to_sendable_msg()?; + smtp::send(&account, &msg)?; + imap_conn.append_msg("Sent", &msg.formatted(), &[Flag::Seen])?; imap_conn.add_flags(mbox, uid, "\\Answered")?; + input::remove_draft()?; info!("Message successfully sent"); break; } - input::Choice::Draft => { - debug!("Saving draft message…"); - imap_conn.append_msg("Drafts", &msg.to_vec()?, &[Flag::Seen])?; - info!("Message successfully saved to Drafts"); - break; - } - input::Choice::Edit => { + input::PostEditChoice::Edit => { let content = input::open_editor_with_draft()?; 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), } @@ -449,24 +462,31 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> { loop { match input::post_edit_choice() { Ok(choice) => match choice { - input::Choice::Send => { + input::PostEditChoice::Send => { debug!("Sending message…"); - smtp::send(&account, &msg.to_sendable_msg()?)?; - imap_conn.append_msg("Sent", &msg.to_vec()?, &[Flag::Seen])?; + let msg = msg.to_sendable_msg()?; + smtp::send(&account, &msg)?; + imap_conn.append_msg("Sent", &msg.formatted(), &[Flag::Seen])?; + input::remove_draft()?; info!("Message successfully sent"); break; } - input::Choice::Draft => { - debug!("Saving draft message…"); - imap_conn.append_msg("Drafts", &msg.to_vec()?, &[Flag::Seen])?; - info!("Message successfully saved to Drafts"); - break; - } - input::Choice::Edit => { + input::PostEditChoice::Edit => { let content = input::open_editor_with_draft()?; 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), }