mirror of
https://github.com/soywod/himalaya.git
synced 2024-07-05 09:05:13 +00:00
improve msg arg/handlers + tpl + flag
This commit is contained in:
parent
bc5f9045ce
commit
54493540b4
|
@ -8,11 +8,11 @@ use log::{debug, trace};
|
|||
use native_tls::{self, TlsConnector, TlsStream};
|
||||
use std::{collections::HashSet, convert::TryFrom, iter::FromIterator, net::TcpStream};
|
||||
|
||||
use crate::{
|
||||
domain::{
|
||||
account::entity::Account, config::entity::Config, mbox::entity::Mbox, msg::entity::Msg,
|
||||
},
|
||||
flag::model::Flags,
|
||||
use crate::domain::{
|
||||
account::entity::Account,
|
||||
config::entity::Config,
|
||||
mbox::entity::Mbox,
|
||||
msg::{entity::Msg, flag::entity::Flags},
|
||||
};
|
||||
|
||||
type ImapSession = imap::Session<TlsStream<TcpStream>>;
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -5,8 +5,7 @@ use mailparse;
|
|||
|
||||
use super::{attachment::Attachment, body::Body, headers::Headers};
|
||||
use crate::{
|
||||
domain::account::entity::Account,
|
||||
flag::model::Flags,
|
||||
domain::{account::entity::Account, msg::flag::entity::Flags},
|
||||
ui::table::{Cell, Row, Table},
|
||||
};
|
||||
|
||||
|
|
81
src/domain/msg/flag/arg.rs
Normal file
81
src/domain/msg/flag/arg.rs
Normal file
|
@ -0,0 +1,81 @@
|
|||
use anyhow::Result;
|
||||
use clap::{self, App, Arg, ArgMatches, SubCommand};
|
||||
use log::debug;
|
||||
|
||||
use crate::domain::msg;
|
||||
|
||||
type Uid<'a> = &'a str;
|
||||
type Flags<'a> = &'a str;
|
||||
|
||||
/// Enumeration of all possible matches.
|
||||
pub enum Match<'a> {
|
||||
Set(Uid<'a>, Flags<'a>),
|
||||
Add(Uid<'a>, Flags<'a>),
|
||||
Remove(Uid<'a>, Flags<'a>),
|
||||
}
|
||||
|
||||
/// Message flag arg matcher.
|
||||
pub fn matches<'a>(m: &'a ArgMatches) -> Result<Option<Match<'a>>> {
|
||||
if let Some(m) = m.subcommand_matches("set") {
|
||||
debug!("set command matched");
|
||||
let uid = m.value_of("uid").unwrap();
|
||||
debug!("uid: {}", uid);
|
||||
let flags = m.value_of("flags").unwrap();
|
||||
debug!("flags: {}", flags);
|
||||
return Ok(Some(Match::Set(uid, flags)));
|
||||
}
|
||||
|
||||
if let Some(m) = m.subcommand_matches("add") {
|
||||
debug!("add command matched");
|
||||
let uid = m.value_of("uid").unwrap();
|
||||
debug!("uid: {}", uid);
|
||||
let flags = m.value_of("flags").unwrap();
|
||||
debug!("flags: {}", flags);
|
||||
return Ok(Some(Match::Add(uid, flags)));
|
||||
}
|
||||
|
||||
if let Some(m) = m.subcommand_matches("remove") {
|
||||
debug!("remove command matched");
|
||||
let uid = m.value_of("uid").unwrap();
|
||||
debug!("uid: {}", uid);
|
||||
let flags = m.value_of("flags").unwrap();
|
||||
debug!("flags: {}", flags);
|
||||
return Ok(Some(Match::Remove(uid, flags)));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Message flag arg.
|
||||
fn flags_arg<'a>() -> Arg<'a, 'a> {
|
||||
Arg::with_name("flags")
|
||||
.help("IMAP flags (see https://tools.ietf.org/html/rfc3501#page-11). Just write the flag name without the backslash. Example: --flags \"Seen Answered\"")
|
||||
.value_name("FLAGS…")
|
||||
.multiple(true)
|
||||
.required(true)
|
||||
}
|
||||
|
||||
/// Message flag subcommands.
|
||||
pub fn subcmds<'a>() -> Vec<App<'a, 'a>> {
|
||||
vec![SubCommand::with_name("flags")
|
||||
.about("Handles flags")
|
||||
.subcommand(
|
||||
SubCommand::with_name("set")
|
||||
.about("Replaces all message flags")
|
||||
.arg(msg::arg::uid_arg())
|
||||
.arg(flags_arg()),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("add")
|
||||
.about("Appends flags to a message")
|
||||
.arg(msg::arg::uid_arg())
|
||||
.arg(flags_arg()),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("remove")
|
||||
.aliases(&["rm"])
|
||||
.about("Removes flags from a message")
|
||||
.arg(msg::arg::uid_arg())
|
||||
.arg(flags_arg()),
|
||||
)]
|
||||
}
|
|
@ -205,8 +205,7 @@ fn convert_to_static<'func>(flag: &'func Flag) -> Result<Flag<'static>, ()> {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use crate::flag::model::Flags;
|
||||
use crate::domain::msg::flag::entity::Flags;
|
||||
use imap::types::Flag;
|
||||
use std::collections::HashSet;
|
||||
|
36
src/domain/msg/flag/handler.rs
Normal file
36
src/domain/msg/flag/handler.rs
Normal file
|
@ -0,0 +1,36 @@
|
|||
use anyhow::Result;
|
||||
|
||||
use crate::domain::{imap::service::ImapServiceInterface, msg::flag::entity::Flags};
|
||||
|
||||
pub fn set<ImapService: ImapServiceInterface>(
|
||||
uid: &str,
|
||||
flags: &str,
|
||||
imap: &mut ImapService,
|
||||
) -> Result<()> {
|
||||
let flags = Flags::from(flags);
|
||||
imap.set_flags(uid, flags)?;
|
||||
imap.logout()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add<ImapService: ImapServiceInterface>(
|
||||
uid: &str,
|
||||
flags: &str,
|
||||
imap: &mut ImapService,
|
||||
) -> Result<()> {
|
||||
let flags = Flags::from(flags);
|
||||
imap.add_flags(uid, flags)?;
|
||||
imap.logout()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn remove<ImapService: ImapServiceInterface>(
|
||||
uid: &str,
|
||||
flags: &str,
|
||||
imap: &mut ImapService,
|
||||
) -> Result<()> {
|
||||
let flags = Flags::from(flags);
|
||||
imap.remove_flags(uid, flags)?;
|
||||
imap.logout()?;
|
||||
Ok(())
|
||||
}
|
5
src/domain/msg/flag/mod.rs
Normal file
5
src/domain/msg/flag/mod.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
//! Module related to messages flag.
|
||||
|
||||
pub mod arg;
|
||||
pub mod entity;
|
||||
pub mod handler;
|
430
src/domain/msg/handler.rs
Normal file
430
src/domain/msg/handler.rs
Normal file
|
@ -0,0 +1,430 @@
|
|||
use anyhow::{Context, Result};
|
||||
use atty::Stream;
|
||||
use imap::types::Flag;
|
||||
use lettre::message::header::ContentTransferEncoding;
|
||||
use log::{debug, error, trace};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
convert::TryFrom,
|
||||
fs,
|
||||
io::{self, BufRead},
|
||||
};
|
||||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
domain::{
|
||||
account::entity::Account,
|
||||
imap::service::ImapServiceInterface,
|
||||
mbox::entity::Mbox,
|
||||
msg::{
|
||||
body::Body,
|
||||
entity::{Msg, Msgs},
|
||||
},
|
||||
smtp::service::SmtpServiceInterface,
|
||||
},
|
||||
input,
|
||||
output::service::{OutputService, OutputServiceInterface},
|
||||
};
|
||||
|
||||
use super::{entity::MsgSerialized, flag::entity::Flags, headers::Headers};
|
||||
|
||||
// TODO: move this function to the right folder
|
||||
fn msg_interaction<ImapService: ImapServiceInterface, SmtpService: SmtpServiceInterface>(
|
||||
output: &OutputService,
|
||||
msg: &mut Msg,
|
||||
imap: &mut ImapService,
|
||||
smtp: &mut SmtpService,
|
||||
) -> Result<bool> {
|
||||
// let the user change the body a little bit first, before opening the prompt
|
||||
msg.edit_body()?;
|
||||
|
||||
loop {
|
||||
match input::post_edit_choice() {
|
||||
Ok(choice) => match choice {
|
||||
input::PostEditChoice::Send => {
|
||||
debug!("sending message…");
|
||||
|
||||
// prepare the msg to be send
|
||||
let sendable = match msg.to_sendable_msg() {
|
||||
Ok(sendable) => sendable,
|
||||
// In general if an error occured, then this is normally
|
||||
// due to a missing value of a header. So let's give the
|
||||
// user another try and give him/her the chance to fix
|
||||
// that :)
|
||||
Err(err) => {
|
||||
println!("{}", err);
|
||||
println!("Please reedit your msg to make it to a sendable message!");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
smtp.send(&sendable)?;
|
||||
|
||||
// TODO: Gmail sent mailboxes are called `[Gmail]/Sent`
|
||||
// which creates a conflict, fix this!
|
||||
|
||||
// let the server know, that the user sent a msg
|
||||
msg.flags.insert(Flag::Seen);
|
||||
let mbox = Mbox::from("Sent");
|
||||
imap.append_msg(&mbox, msg)?;
|
||||
|
||||
// remove the draft, since we sent it
|
||||
input::remove_draft()?;
|
||||
output.print("Message successfully sent")?;
|
||||
break;
|
||||
}
|
||||
// edit the body of the msg
|
||||
input::PostEditChoice::Edit => {
|
||||
// Did something goes wrong when the user changed the
|
||||
// content?
|
||||
if let Err(err) = msg.edit_body() {
|
||||
println!("[ERROR] {}", err);
|
||||
println!(concat!(
|
||||
"Please try to fix the problem by editing",
|
||||
"the msg again."
|
||||
));
|
||||
}
|
||||
}
|
||||
input::PostEditChoice::LocalDraft => break,
|
||||
input::PostEditChoice::RemoteDraft => {
|
||||
debug!("saving to draft…");
|
||||
|
||||
msg.flags.insert(Flag::Seen);
|
||||
|
||||
let mbox = Mbox::from("Drafts");
|
||||
match imap.append_msg(&mbox, msg) {
|
||||
Ok(_) => {
|
||||
input::remove_draft()?;
|
||||
output.print("Message successfully saved to Drafts")?;
|
||||
}
|
||||
Err(err) => {
|
||||
output.print("Cannot save draft to the server")?;
|
||||
return Err(err.into());
|
||||
}
|
||||
};
|
||||
break;
|
||||
}
|
||||
input::PostEditChoice::Discard => {
|
||||
input::remove_draft()?;
|
||||
break;
|
||||
}
|
||||
},
|
||||
Err(err) => error!("{}", err),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub fn attachments<ImapService: ImapServiceInterface>(
|
||||
uid: &str,
|
||||
account: &Account,
|
||||
output: &OutputService,
|
||||
imap: &mut ImapService,
|
||||
) -> Result<()> {
|
||||
let msg = imap.get_msg(&uid)?;
|
||||
let attachments = msg.attachments.clone();
|
||||
|
||||
debug!(
|
||||
"{} attachment(s) found for message {}",
|
||||
&attachments.len(),
|
||||
&uid
|
||||
);
|
||||
|
||||
// Iterate through all attachments and download them to the download
|
||||
// directory of the account.
|
||||
for attachment in &attachments {
|
||||
let filepath = account.downloads_dir.join(&attachment.filename);
|
||||
debug!("downloading {}…", &attachment.filename);
|
||||
fs::write(&filepath, &attachment.body_raw)
|
||||
.context(format!("cannot save attachment {:?}", filepath))?;
|
||||
}
|
||||
|
||||
debug!(
|
||||
"{} attachment(s) successfully downloaded",
|
||||
&attachments.len()
|
||||
);
|
||||
|
||||
output.print(format!(
|
||||
"{} attachment(s) successfully downloaded",
|
||||
&attachments.len()
|
||||
))?;
|
||||
|
||||
imap.logout()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn copy<ImapService: ImapServiceInterface>(
|
||||
uid: &str,
|
||||
mbox: Option<&str>,
|
||||
output: &OutputService,
|
||||
imap: &mut ImapService,
|
||||
) -> Result<()> {
|
||||
let target = Mbox::try_from(mbox)?;
|
||||
let mut msg = imap.get_msg(&uid)?;
|
||||
// the message, which will be in the new mailbox doesn't need to be seen
|
||||
msg.flags.insert(Flag::Seen);
|
||||
imap.append_msg(&target, &mut msg)?;
|
||||
debug!("message {} successfully copied to folder `{}`", uid, target);
|
||||
output.print(format!(
|
||||
"Message {} successfully copied to folder `{}`",
|
||||
uid, target
|
||||
))?;
|
||||
imap.logout()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn delete<ImapService: ImapServiceInterface>(
|
||||
uid: &str,
|
||||
output: &OutputService,
|
||||
imap: &mut ImapService,
|
||||
) -> Result<()> {
|
||||
let flags = vec![Flag::Seen, Flag::Deleted];
|
||||
imap.add_flags(uid, Flags::from(flags))?;
|
||||
imap.expunge()?;
|
||||
debug!("message {} successfully deleted", uid);
|
||||
output.print(format!("Message {} successfully deleted", uid))?;
|
||||
imap.logout()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn forward<ImapService: ImapServiceInterface, SmtpService: SmtpServiceInterface>(
|
||||
uid: &str,
|
||||
attachments_paths: Vec<&str>,
|
||||
account: &Account,
|
||||
output: &OutputService,
|
||||
imap: &mut ImapService,
|
||||
smtp: &mut SmtpService,
|
||||
) -> Result<()> {
|
||||
let mut msg = imap.get_msg(&uid)?;
|
||||
// prepare to forward it
|
||||
msg.change_to_forwarding(&account);
|
||||
attachments_paths
|
||||
.iter()
|
||||
.for_each(|path| msg.add_attachment(path));
|
||||
debug!("found {} attachments", attachments_paths.len());
|
||||
trace!("attachments: {:?}", attachments_paths);
|
||||
// apply changes
|
||||
msg_interaction(output, &mut msg, imap, smtp)?;
|
||||
imap.logout()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn list<ImapService: ImapServiceInterface>(
|
||||
page_size: Option<usize>,
|
||||
page: usize,
|
||||
account: &Account,
|
||||
output: &OutputService,
|
||||
imap: &mut ImapService,
|
||||
) -> Result<()> {
|
||||
let page_size = page_size.unwrap_or(account.default_page_size);
|
||||
let msgs = imap.list_msgs(&page_size, &page)?;
|
||||
let msgs = if let Some(ref fetches) = msgs {
|
||||
Msgs::try_from(fetches)?
|
||||
} else {
|
||||
Msgs::new()
|
||||
};
|
||||
trace!("messages: {:#?}", msgs);
|
||||
output.print(msgs)?;
|
||||
imap.logout()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn mailto<ImapService: ImapServiceInterface, SmtpService: SmtpServiceInterface>(
|
||||
url: &Url,
|
||||
account: &Account,
|
||||
output: &OutputService,
|
||||
imap: &mut ImapService,
|
||||
smtp: &mut SmtpService,
|
||||
) -> Result<()> {
|
||||
let mut cc = Vec::new();
|
||||
let mut bcc = Vec::new();
|
||||
let mut subject = Cow::default();
|
||||
let mut body = Cow::default();
|
||||
|
||||
for (key, val) in url.query_pairs() {
|
||||
match key.as_bytes() {
|
||||
b"cc" => {
|
||||
cc.push(val.into());
|
||||
}
|
||||
b"bcc" => {
|
||||
bcc.push(val.into());
|
||||
}
|
||||
b"subject" => {
|
||||
subject = val;
|
||||
}
|
||||
b"body" => {
|
||||
body = val;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
let headers = Headers {
|
||||
from: vec![account.address()],
|
||||
to: vec![url.path().to_string()],
|
||||
encoding: ContentTransferEncoding::Base64,
|
||||
bcc: Some(bcc),
|
||||
cc: Some(cc),
|
||||
signature: Some(account.signature.to_owned()),
|
||||
subject: Some(subject.into()),
|
||||
..Headers::default()
|
||||
};
|
||||
|
||||
let mut msg = Msg::new_with_headers(&account, headers);
|
||||
msg.body = Body::new_with_text(body);
|
||||
msg_interaction(output, &mut msg, imap, smtp)?;
|
||||
imap.logout()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn move_<ImapService: ImapServiceInterface>(
|
||||
uid: &str,
|
||||
mbox: Option<&str>,
|
||||
output: &OutputService,
|
||||
imap: &mut ImapService,
|
||||
) -> Result<()> {
|
||||
let target = Mbox::try_from(mbox)?;
|
||||
let mut msg = imap.get_msg(&uid)?;
|
||||
// create the msg in the target-msgbox
|
||||
msg.flags.insert(Flag::Seen);
|
||||
imap.append_msg(&target, &mut msg)?;
|
||||
debug!("message {} successfully moved to folder `{}`", uid, target);
|
||||
output.print(format!(
|
||||
"Message {} successfully moved to folder `{}`",
|
||||
uid, target
|
||||
))?;
|
||||
// delete the msg in the old mailbox
|
||||
let flags = vec![Flag::Seen, Flag::Deleted];
|
||||
imap.add_flags(uid, Flags::from(flags))?;
|
||||
imap.expunge()?;
|
||||
imap.logout()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn read<ImapService: ImapServiceInterface>(
|
||||
uid: &str,
|
||||
// TODO: use the mime to select the right body
|
||||
_mime: String,
|
||||
raw: bool,
|
||||
output: &OutputService,
|
||||
imap: &mut ImapService,
|
||||
) -> Result<()> {
|
||||
let msg = imap.get_msg(&uid)?;
|
||||
if raw {
|
||||
output.print(msg.get_raw_as_string()?)?;
|
||||
} else {
|
||||
output.print(MsgSerialized::try_from(&msg)?)?;
|
||||
}
|
||||
imap.logout()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn reply<ImapService: ImapServiceInterface, SmtpService: SmtpServiceInterface>(
|
||||
uid: &str,
|
||||
all: bool,
|
||||
attachments_paths: Vec<&str>,
|
||||
account: &Account,
|
||||
output: &OutputService,
|
||||
imap: &mut ImapService,
|
||||
smtp: &mut SmtpService,
|
||||
) -> Result<()> {
|
||||
let mut msg = imap.get_msg(&uid)?;
|
||||
// Change the msg to a reply-msg.
|
||||
msg.change_to_reply(&account, all)?;
|
||||
// Apply the given attachments to the reply-msg.
|
||||
attachments_paths
|
||||
.iter()
|
||||
.for_each(|path| msg.add_attachment(path));
|
||||
debug!("found {} attachments", attachments_paths.len());
|
||||
trace!("attachments: {:#?}", attachments_paths);
|
||||
msg_interaction(output, &mut msg, imap, smtp)?;
|
||||
imap.logout()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn save<ImapService: ImapServiceInterface>(
|
||||
mbox: Option<&str>,
|
||||
msg: &str,
|
||||
imap: &mut ImapService,
|
||||
) -> Result<()> {
|
||||
let mbox = Mbox::try_from(mbox)?;
|
||||
let mut msg = Msg::try_from(msg)?;
|
||||
msg.flags.insert(Flag::Seen);
|
||||
imap.append_msg(&mbox, &mut msg)?;
|
||||
imap.logout()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn search<ImapService: ImapServiceInterface>(
|
||||
page_size: Option<usize>,
|
||||
page: usize,
|
||||
query: String,
|
||||
account: &Account,
|
||||
output: &OutputService,
|
||||
imap: &mut ImapService,
|
||||
) -> Result<()> {
|
||||
let page_size = page_size.unwrap_or(account.default_page_size);
|
||||
let msgs = imap.search_msgs(&query, &page_size, &page)?;
|
||||
let msgs = if let Some(ref fetches) = msgs {
|
||||
Msgs::try_from(fetches)?
|
||||
} else {
|
||||
Msgs::new()
|
||||
};
|
||||
trace!("messages: {:?}", msgs);
|
||||
output.print(msgs)?;
|
||||
imap.logout()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn send<ImapService: ImapServiceInterface, SmtpService: SmtpServiceInterface>(
|
||||
msg: &str,
|
||||
output: &OutputService,
|
||||
imap: &mut ImapService,
|
||||
smtp: &mut SmtpService,
|
||||
) -> Result<()> {
|
||||
let msg = if atty::is(Stream::Stdin) || output.is_json() {
|
||||
msg.replace("\r", "").replace("\n", "\r\n")
|
||||
} else {
|
||||
io::stdin()
|
||||
.lock()
|
||||
.lines()
|
||||
.filter_map(|ln| ln.ok())
|
||||
.map(|ln| ln.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join("\r\n")
|
||||
};
|
||||
let mut msg = Msg::try_from(msg.as_str())?;
|
||||
// send the message/msg
|
||||
let sendable = msg.to_sendable_msg()?;
|
||||
smtp.send(&sendable)?;
|
||||
debug!("message sent!");
|
||||
// add the message/msg to the Sent-Mailbox of the user
|
||||
msg.flags.insert(Flag::Seen);
|
||||
let mbox = Mbox::from("Sent");
|
||||
imap.append_msg(&mbox, &mut msg)?;
|
||||
imap.logout()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write<ImapService: ImapServiceInterface, SmtpService: SmtpServiceInterface>(
|
||||
attachments_paths: Vec<&str>,
|
||||
account: &Account,
|
||||
output: &OutputService,
|
||||
imap: &mut ImapService,
|
||||
smtp: &mut SmtpService,
|
||||
) -> Result<()> {
|
||||
let mut msg = Msg::new_with_headers(
|
||||
&account,
|
||||
Headers {
|
||||
subject: Some(String::new()),
|
||||
to: Vec::new(),
|
||||
..Headers::default()
|
||||
},
|
||||
);
|
||||
attachments_paths
|
||||
.iter()
|
||||
.for_each(|path| msg.add_attachment(path));
|
||||
msg_interaction(output, &mut msg, imap, smtp)?;
|
||||
imap.logout()?;
|
||||
Ok(())
|
||||
}
|
|
@ -35,3 +35,6 @@ pub mod headers;
|
|||
/// This module is used in the `Msg` struct, which should represent the body of
|
||||
/// a msg; The part where you're writing some text like `Dear Mr. LMAO`.
|
||||
pub mod body;
|
||||
pub mod flag;
|
||||
pub mod handler;
|
||||
pub mod tpl;
|
||||
|
|
120
src/domain/msg/tpl/arg.rs
Normal file
120
src/domain/msg/tpl/arg.rs
Normal file
|
@ -0,0 +1,120 @@
|
|||
use anyhow::Result;
|
||||
use clap::{self, App, Arg, ArgMatches, SubCommand};
|
||||
use log::debug;
|
||||
|
||||
use crate::domain::msg::{self, arg::uid_arg};
|
||||
|
||||
type Uid<'a> = &'a str;
|
||||
type All = bool;
|
||||
|
||||
/// Enumeration of all possible matches.
|
||||
pub enum Match<'a> {
|
||||
New,
|
||||
Reply(Uid<'a>, All),
|
||||
Forward(Uid<'a>),
|
||||
}
|
||||
|
||||
/// Message template arg matcher.
|
||||
pub fn matches<'a>(m: &'a ArgMatches) -> Result<Option<Match<'a>>> {
|
||||
if let Some(_) = m.subcommand_matches("new") {
|
||||
debug!("new command matched");
|
||||
return Ok(Some(Match::New));
|
||||
}
|
||||
|
||||
if let Some(m) = m.subcommand_matches("reply") {
|
||||
debug!("reply command matched");
|
||||
let uid = m.value_of("uid").unwrap();
|
||||
debug!("uid: {}", uid);
|
||||
let all = m.is_present("reply-all");
|
||||
debug!("reply all: {}", all);
|
||||
return Ok(Some(Match::Reply(uid, all)));
|
||||
}
|
||||
|
||||
if let Some(m) = m.subcommand_matches("forward") {
|
||||
debug!("forward command matched");
|
||||
let uid = m.value_of("uid").unwrap();
|
||||
debug!("uid: {}", uid);
|
||||
return Ok(Some(Match::Forward(uid)));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Message template args.
|
||||
pub fn tpl_args<'a>() -> Vec<Arg<'a, 'a>> {
|
||||
vec![
|
||||
Arg::with_name("subject")
|
||||
.help("Overrides the Subject header")
|
||||
.short("s")
|
||||
.long("subject")
|
||||
.value_name("STRING"),
|
||||
Arg::with_name("from")
|
||||
.help("Overrides the From header")
|
||||
.short("f")
|
||||
.long("from")
|
||||
.value_name("ADDR")
|
||||
.multiple(true),
|
||||
Arg::with_name("to")
|
||||
.help("Overrides the To header")
|
||||
.short("t")
|
||||
.long("to")
|
||||
.value_name("ADDR")
|
||||
.multiple(true),
|
||||
Arg::with_name("cc")
|
||||
.help("Overrides the Cc header")
|
||||
.short("c")
|
||||
.long("cc")
|
||||
.value_name("ADDR")
|
||||
.multiple(true),
|
||||
Arg::with_name("bcc")
|
||||
.help("Overrides the Bcc header")
|
||||
.short("b")
|
||||
.long("bcc")
|
||||
.value_name("ADDR")
|
||||
.multiple(true),
|
||||
Arg::with_name("header")
|
||||
.help("Overrides a specific header")
|
||||
.short("h")
|
||||
.long("header")
|
||||
.value_name("KEY: VAL")
|
||||
.multiple(true),
|
||||
Arg::with_name("body")
|
||||
.help("Overrides the body")
|
||||
.short("B")
|
||||
.long("body")
|
||||
.value_name("STRING"),
|
||||
Arg::with_name("signature")
|
||||
.help("Overrides the signature")
|
||||
.short("S")
|
||||
.long("signature")
|
||||
.value_name("STRING"),
|
||||
]
|
||||
}
|
||||
|
||||
/// Message template subcommands.
|
||||
pub fn subcmds<'a>() -> Vec<App<'a, 'a>> {
|
||||
vec![SubCommand::with_name("template")
|
||||
.aliases(&["tpl"])
|
||||
.about("Generates a message template")
|
||||
.subcommand(
|
||||
SubCommand::with_name("new")
|
||||
.aliases(&["n"])
|
||||
.about("Generates a new message template")
|
||||
.args(&tpl_args()),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("reply")
|
||||
.aliases(&["rep", "r"])
|
||||
.about("Generates a reply message template")
|
||||
.arg(uid_arg())
|
||||
.arg(msg::arg::reply_all_arg())
|
||||
.args(&tpl_args()),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("forward")
|
||||
.aliases(&["fwd", "fw", "f"])
|
||||
.about("Generates a forward message template")
|
||||
.arg(uid_arg())
|
||||
.args(&tpl_args()),
|
||||
)]
|
||||
}
|
150
src/domain/msg/tpl/handler.rs
Normal file
150
src/domain/msg/tpl/handler.rs
Normal file
|
@ -0,0 +1,150 @@
|
|||
use std::convert::TryFrom;
|
||||
|
||||
use anyhow::Result;
|
||||
use log::trace;
|
||||
|
||||
use crate::{
|
||||
domain::{
|
||||
account::entity::Account,
|
||||
imap::service::ImapServiceInterface,
|
||||
msg::entity::{Msg, MsgSerialized},
|
||||
},
|
||||
output::service::OutputServiceInterface,
|
||||
};
|
||||
|
||||
pub fn new<OutputService: OutputServiceInterface, ImapService: ImapServiceInterface>(
|
||||
account: &Account,
|
||||
output: &OutputService,
|
||||
imap: &mut ImapService,
|
||||
) -> Result<()> {
|
||||
let msg = Msg::new(&account);
|
||||
// FIXME
|
||||
// override_msg_with_args(&mut msg, &matches);
|
||||
trace!("message: {:#?}", msg);
|
||||
output.print(MsgSerialized::try_from(&msg)?)?;
|
||||
imap.logout()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn reply<OutputService: OutputServiceInterface, ImapService: ImapServiceInterface>(
|
||||
uid: &str,
|
||||
all: bool,
|
||||
account: &Account,
|
||||
output: &OutputService,
|
||||
imap: &mut ImapService,
|
||||
) -> Result<()> {
|
||||
let mut msg = imap.get_msg(&uid)?;
|
||||
msg.change_to_reply(&account, all)?;
|
||||
// FIXME
|
||||
// override_msg_with_args(&mut msg, &matches);
|
||||
trace!("Message: {:?}", msg);
|
||||
output.print(MsgSerialized::try_from(&msg)?)?;
|
||||
imap.logout()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn forward<OutputService: OutputServiceInterface, ImapService: ImapServiceInterface>(
|
||||
uid: &str,
|
||||
account: &Account,
|
||||
output: &OutputService,
|
||||
imap: &mut ImapService,
|
||||
) -> Result<()> {
|
||||
let mut msg = imap.get_msg(&uid)?;
|
||||
msg.change_to_forwarding(&account);
|
||||
// FIXME
|
||||
// override_msg_with_args(&mut msg, &matches);
|
||||
trace!("Message: {:?}", msg);
|
||||
output.print(MsgSerialized::try_from(&msg)?)?;
|
||||
imap.logout()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// == Helper functions ==
|
||||
// -- Template Subcommands --
|
||||
// These functions are more used for the "template" subcommand
|
||||
// fn override_msg_with_args(msg: &mut Msg) {
|
||||
// // -- Collecting credentials --
|
||||
// let from: Vec<String> = match matches.values_of("from") {
|
||||
// Some(from) => from.map(|arg| arg.to_string()).collect(),
|
||||
// None => msg.headers.from.clone(),
|
||||
// };
|
||||
|
||||
// let to: Vec<String> = match matches.values_of("to") {
|
||||
// Some(to) => to.map(|arg| arg.to_string()).collect(),
|
||||
// None => Vec::new(),
|
||||
// };
|
||||
|
||||
// let subject = matches
|
||||
// .value_of("subject")
|
||||
// .and_then(|subject| Some(subject.to_string()));
|
||||
|
||||
// let cc: Option<Vec<String>> = matches
|
||||
// .values_of("cc")
|
||||
// .and_then(|cc| Some(cc.map(|arg| arg.to_string()).collect()));
|
||||
|
||||
// let bcc: Option<Vec<String>> = matches
|
||||
// .values_of("bcc")
|
||||
// .and_then(|bcc| Some(bcc.map(|arg| arg.to_string()).collect()));
|
||||
|
||||
// let signature = matches
|
||||
// .value_of("signature")
|
||||
// .and_then(|signature| Some(signature.to_string()))
|
||||
// .or(msg.headers.signature.clone());
|
||||
|
||||
// let custom_headers: Option<HashMap<String, Vec<String>>> = {
|
||||
// if let Some(matched_headers) = matches.values_of("header") {
|
||||
// let mut custom_headers: HashMap<String, Vec<String>> = HashMap::new();
|
||||
|
||||
// // collect the custom headers
|
||||
// for header in matched_headers {
|
||||
// let mut header = header.split(":");
|
||||
// let key = header.next().unwrap_or_default();
|
||||
// let val = header.next().unwrap_or_default().trim_start();
|
||||
|
||||
// debug!("overriden header: {}={}", key, val);
|
||||
|
||||
// custom_headers.insert(key.to_string(), vec![val.to_string()]);
|
||||
// }
|
||||
|
||||
// Some(custom_headers)
|
||||
// } else {
|
||||
// None
|
||||
// }
|
||||
// };
|
||||
|
||||
// let body = {
|
||||
// if atty::isnt(Stream::Stdin) {
|
||||
// let body = io::stdin()
|
||||
// .lock()
|
||||
// .lines()
|
||||
// .filter_map(|line| line.ok())
|
||||
// .map(|line| line.to_string())
|
||||
// .collect::<Vec<String>>()
|
||||
// .join("\n");
|
||||
// debug!("overriden body from stdin: {:?}", body);
|
||||
// body
|
||||
// } else if let Some(body) = matches.value_of("body") {
|
||||
// debug!("overriden body: {:?}", body);
|
||||
// body.to_string()
|
||||
// } else {
|
||||
// String::new()
|
||||
// }
|
||||
// };
|
||||
|
||||
// let body = Body::new_with_text(body);
|
||||
|
||||
// // -- Creating and printing --
|
||||
// let headers = Headers {
|
||||
// from,
|
||||
// subject,
|
||||
// to,
|
||||
// cc,
|
||||
// bcc,
|
||||
// signature,
|
||||
// custom_headers,
|
||||
// ..msg.headers.clone()
|
||||
// };
|
||||
|
||||
// msg.headers = headers;
|
||||
// msg.body = body;
|
||||
// }
|
4
src/domain/msg/tpl/mod.rs
Normal file
4
src/domain/msg/tpl/mod.rs
Normal file
|
@ -0,0 +1,4 @@
|
|||
//! Module related to messages template.
|
||||
|
||||
pub mod arg;
|
||||
pub mod handler;
|
|
@ -1,93 +0,0 @@
|
|||
use anyhow::Result;
|
||||
use clap;
|
||||
use log::debug;
|
||||
|
||||
use crate::{
|
||||
domain::{imap::service::ImapServiceInterface, msg::arg::uid_arg},
|
||||
flag::model::Flags,
|
||||
};
|
||||
|
||||
fn flags_arg<'a>() -> clap::Arg<'a, 'a> {
|
||||
clap::Arg::with_name("flags")
|
||||
.help("IMAP flags (see https://tools.ietf.org/html/rfc3501#page-11). Just write the flag name without the backslash. Example: --flags \"Seen Answered\"")
|
||||
.value_name("FLAGS…")
|
||||
.multiple(true)
|
||||
.required(true)
|
||||
}
|
||||
|
||||
pub fn subcmds<'a>() -> Vec<clap::App<'a, 'a>> {
|
||||
vec![clap::SubCommand::with_name("flags")
|
||||
.about("Handles flags")
|
||||
.subcommand(
|
||||
clap::SubCommand::with_name("set")
|
||||
.about("Replaces all message flags")
|
||||
.arg(uid_arg())
|
||||
.arg(flags_arg()),
|
||||
)
|
||||
.subcommand(
|
||||
clap::SubCommand::with_name("add")
|
||||
.about("Appends flags to a message")
|
||||
.arg(uid_arg())
|
||||
.arg(flags_arg()),
|
||||
)
|
||||
.subcommand(
|
||||
clap::SubCommand::with_name("remove")
|
||||
.aliases(&["rm"])
|
||||
.about("Removes flags from a message")
|
||||
.arg(uid_arg())
|
||||
.arg(flags_arg()),
|
||||
)]
|
||||
}
|
||||
|
||||
pub fn matches<ImapService: ImapServiceInterface>(
|
||||
arg_matches: &clap::ArgMatches,
|
||||
imap: &mut ImapService,
|
||||
) -> Result<bool> {
|
||||
if let Some(matches) = arg_matches.subcommand_matches("set") {
|
||||
debug!("set command matched");
|
||||
|
||||
let uid = matches.value_of("uid").unwrap();
|
||||
debug!("uid: {}", uid);
|
||||
|
||||
let flags = matches.value_of("flags").unwrap();
|
||||
debug!("flags: {}", flags);
|
||||
let flags = Flags::from(flags);
|
||||
|
||||
imap.set_flags(uid, flags)?;
|
||||
imap.logout()?;
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
if let Some(matches) = arg_matches.subcommand_matches("add") {
|
||||
debug!("add command matched");
|
||||
|
||||
let uid = matches.value_of("uid").unwrap();
|
||||
debug!("uid: {}", uid);
|
||||
|
||||
let flags = matches.value_of("flags").unwrap();
|
||||
debug!("flags: {}", flags);
|
||||
let flags = Flags::from(flags);
|
||||
|
||||
imap.add_flags(uid, flags)?;
|
||||
imap.logout()?;
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
if let Some(matches) = arg_matches.subcommand_matches("remove") {
|
||||
debug!("remove command matched");
|
||||
|
||||
let uid = matches.value_of("uid").unwrap();
|
||||
debug!("uid: {}", uid);
|
||||
|
||||
let flags = matches.value_of("flags").unwrap();
|
||||
debug!("flags: {}", flags);
|
||||
let flags = Flags::from(flags);
|
||||
|
||||
imap.remove_flags(uid, flags)?;
|
||||
imap.logout()?;
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
debug!("nothing matched");
|
||||
Ok(false)
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
pub mod arg;
|
||||
pub mod model;
|
|
@ -17,10 +17,6 @@ pub mod compl;
|
|||
/// Everything which is related to the config files. For example the structure of your config file.
|
||||
pub mod config;
|
||||
|
||||
/// A wrapper for representing a flag of a message or mailbox. For example the delete-flag or
|
||||
/// read-flag.
|
||||
pub mod flag;
|
||||
|
||||
/// Handles the input-interaction with the user. For example if you want to edit the body of your
|
||||
/// message, his module takes care of the draft and calls your ~(neo)vim~ your favourite editor.
|
||||
pub mod input;
|
||||
|
|
96
src/main.rs
96
src/main.rs
|
@ -2,6 +2,7 @@ use anyhow::Result;
|
|||
use clap;
|
||||
use env_logger;
|
||||
use std::{convert::TryFrom, env};
|
||||
use url::Url;
|
||||
|
||||
use himalaya::{
|
||||
compl,
|
||||
|
@ -14,7 +15,6 @@ use himalaya::{
|
|||
msg,
|
||||
smtp::service::SmtpService,
|
||||
},
|
||||
flag,
|
||||
output::{cli::output_args, service::OutputService},
|
||||
};
|
||||
|
||||
|
@ -26,11 +26,10 @@ fn create_app<'a>() -> clap::App<'a, 'a> {
|
|||
.args(&output_args())
|
||||
.args(&config_args())
|
||||
.arg(mbox::arg::source_arg())
|
||||
.subcommands(flag::arg::subcmds())
|
||||
.subcommands(compl::arg::subcmds())
|
||||
.subcommands(imap::arg::subcmds())
|
||||
.subcommands(mbox::arg::subcmds())
|
||||
.subcommands(msg::arg::subcmds())
|
||||
.subcommands(compl::arg::subcmds())
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
|
@ -39,19 +38,18 @@ fn main() -> Result<()> {
|
|||
env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "off"),
|
||||
);
|
||||
|
||||
// TODO: put in a `mailto` module
|
||||
// let raw_args: Vec<String> = env::args().collect();
|
||||
// if raw_args.len() > 1 && raw_args[1].starts_with("mailto:") {
|
||||
// let config = Config::new(None)?;
|
||||
// let account = config.find_account_by_name(None)?.clone();
|
||||
// let output = Output::new("plain");
|
||||
// let mbox = "INBOX";
|
||||
// let arg_matches = ArgMatches::default();
|
||||
// let app = Ctx::new(config, output, mbox, arg_matches);
|
||||
// let url = Url::parse(&raw_args[1])?;
|
||||
// let smtp = domain::smtp::service::SMTPService::new(&app.account);
|
||||
// return Ok(msg_matches_mailto(&app, &url, smtp)?);
|
||||
// }
|
||||
// Check mailto match BEFORE app initialization.
|
||||
let raw_args: Vec<String> = env::args().collect();
|
||||
if raw_args.len() > 1 && raw_args[1].starts_with("mailto:") {
|
||||
let mbox = Mbox::from("INBOX");
|
||||
let config = Config::try_from(None)?;
|
||||
let account = Account::try_from((&config, None))?;
|
||||
let output = OutputService::from("plain");
|
||||
let url = Url::parse(&raw_args[1])?;
|
||||
let mut imap = ImapService::from((&account, &mbox));
|
||||
let mut smtp = SmtpService::from(&account);
|
||||
return msg::handler::mailto(&url, &account, &output, &mut imap, &mut smtp);
|
||||
}
|
||||
|
||||
let app = create_app();
|
||||
let m = app.get_matches();
|
||||
|
@ -91,9 +89,69 @@ fn main() -> Result<()> {
|
|||
_ => (),
|
||||
}
|
||||
|
||||
// TODO: use same system as compl
|
||||
let _matched = flag::arg::matches(&m, &mut imap)?
|
||||
|| msg::arg::matches(&m, &mbox, &account, &output, &mut imap, &mut smtp)?;
|
||||
// Check message matches.
|
||||
match msg::arg::matches(&m)? {
|
||||
Some(msg::arg::Match::Attachments(uid)) => {
|
||||
return msg::handler::attachments(uid, &account, &output, &mut imap);
|
||||
}
|
||||
Some(msg::arg::Match::Copy(uid, mbox)) => {
|
||||
return msg::handler::copy(uid, mbox, &output, &mut imap);
|
||||
}
|
||||
Some(msg::arg::Match::Delete(uid)) => {
|
||||
return msg::handler::delete(uid, &output, &mut imap);
|
||||
}
|
||||
Some(msg::arg::Match::Forward(uid, paths)) => {
|
||||
return msg::handler::forward(uid, paths, &account, &output, &mut imap, &mut smtp);
|
||||
}
|
||||
Some(msg::arg::Match::List(page_size, page)) => {
|
||||
return msg::handler::list(page_size, page, &account, &output, &mut imap);
|
||||
}
|
||||
Some(msg::arg::Match::Move(uid, mbox)) => {
|
||||
return msg::handler::move_(uid, mbox, &output, &mut imap);
|
||||
}
|
||||
Some(msg::arg::Match::Read(uid, mime, raw)) => {
|
||||
return msg::handler::read(uid, mime, raw, &output, &mut imap);
|
||||
}
|
||||
Some(msg::arg::Match::Reply(uid, all, paths)) => {
|
||||
return msg::handler::reply(uid, all, paths, &account, &output, &mut imap, &mut smtp);
|
||||
}
|
||||
Some(msg::arg::Match::Save(mbox, msg)) => {
|
||||
return msg::handler::save(mbox, msg, &mut imap);
|
||||
}
|
||||
Some(msg::arg::Match::Search(query, page_size, page)) => {
|
||||
return msg::handler::search(page_size, page, query, &account, &output, &mut imap);
|
||||
}
|
||||
Some(msg::arg::Match::Send(msg)) => {
|
||||
return msg::handler::send(msg, &output, &mut imap, &mut smtp);
|
||||
}
|
||||
Some(msg::arg::Match::Write(paths)) => {
|
||||
return msg::handler::write(paths, &account, &output, &mut imap, &mut smtp);
|
||||
}
|
||||
|
||||
Some(msg::arg::Match::Flag(m)) => match m {
|
||||
msg::flag::arg::Match::Set(uid, flags) => {
|
||||
return msg::flag::handler::set(uid, flags, &mut imap);
|
||||
}
|
||||
msg::flag::arg::Match::Add(uid, flags) => {
|
||||
return msg::flag::handler::add(uid, flags, &mut imap);
|
||||
}
|
||||
msg::flag::arg::Match::Remove(uid, flags) => {
|
||||
return msg::flag::handler::remove(uid, flags, &mut imap);
|
||||
}
|
||||
},
|
||||
Some(msg::arg::Match::Tpl(m)) => match m {
|
||||
msg::tpl::arg::Match::New => {
|
||||
return msg::tpl::handler::new(&account, &output, &mut imap);
|
||||
}
|
||||
msg::tpl::arg::Match::Reply(uid, all) => {
|
||||
return msg::tpl::handler::reply(uid, all, &account, &output, &mut imap);
|
||||
}
|
||||
msg::tpl::arg::Match::Forward(uid) => {
|
||||
return msg::tpl::handler::forward(uid, &account, &output, &mut imap);
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -12,6 +12,15 @@ pub enum OutputFmt {
|
|||
Json,
|
||||
}
|
||||
|
||||
impl From<&str> for OutputFmt {
|
||||
fn from(fmt: &str) -> Self {
|
||||
match fmt {
|
||||
slice if slice.eq_ignore_ascii_case("json") => Self::Json,
|
||||
_ => Self::Plain,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Option<&str>> for OutputFmt {
|
||||
type Error = Error;
|
||||
|
||||
|
@ -99,6 +108,15 @@ impl Default for OutputService {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<&str> for OutputService {
|
||||
fn from(fmt: &str) -> Self {
|
||||
debug!("init output service");
|
||||
debug!("output: `{:?}`", fmt);
|
||||
let fmt = fmt.into();
|
||||
Self { fmt }
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Option<&str>> for OutputService {
|
||||
type Error = Error;
|
||||
|
||||
|
|
Loading…
Reference in a new issue