improve mailbox structure (#223)

* split entities, remove manual serialization, add comments

* add mbox arg subbcmds test

* finalize mbox arg tests

* fix typos in comments

* add missing tests for entities

* rename fetch mbox imap fn
This commit is contained in:
Clément DOUIN 2021-10-13 20:48:42 +02:00 committed by GitHub
parent d21778c35e
commit 8af14734c3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 416 additions and 253 deletions

View file

@ -14,19 +14,16 @@ use std::{
use crate::{
config::{Account, Config},
domain::{
mbox::Mbox,
msg::{Envelopes, Flags, Msg},
},
domain::{Envelopes, Flags, Mbox, Msg},
};
type ImapSession = imap::Session<TlsStream<TcpStream>>;
type ImapMboxes = imap::types::ZeroCopy<Vec<imap::types::Name>>;
pub(crate) type RawMboxes = imap::types::ZeroCopy<Vec<imap::types::Name>>;
pub trait ImapServiceInterface {
fn notify(&mut self, config: &Config, keepalive: u64) -> Result<()>;
fn watch(&mut self, keepalive: u64) -> Result<()>;
fn get_mboxes(&mut self) -> Result<ImapMboxes>;
fn fetch_raw_mboxes(&mut self) -> Result<RawMboxes>;
fn get_msgs(&mut self, page_size: &usize, page: &usize) -> Result<Envelopes>;
fn find_msgs(&mut self, query: &str, page_size: &usize, page: &usize) -> Result<Envelopes>;
fn find_msg(&mut self, seq: &str) -> Result<Msg>;
@ -46,7 +43,7 @@ pub trait ImapServiceInterface {
pub struct ImapService<'a> {
account: &'a Account,
mbox: &'a Mbox,
mbox: &'a Mbox<'a>,
sess: Option<ImapSession>,
}
@ -106,12 +103,10 @@ impl<'a> ImapService<'a> {
}
impl<'a> ImapServiceInterface for ImapService<'a> {
fn get_mboxes(&mut self) -> Result<ImapMboxes> {
let mboxes = self
.sess()?
fn fetch_raw_mboxes(&mut self) -> Result<RawMboxes> {
self.sess()?
.list(Some(""), Some("*"))
.context("cannot list mailboxes")?;
Ok(mboxes)
.context("cannot list mailboxes")
}
fn get_msgs(&mut self, page_size: &usize, page: &usize) -> Result<Envelopes> {
@ -380,7 +375,7 @@ impl<'a> ImapServiceInterface for ImapService<'a> {
}
}
impl<'a> From<(&'a Account, &'a Mbox)> for ImapService<'a> {
impl<'a> From<(&'a Account, &'a Mbox<'a>)> for ImapService<'a> {
fn from((account, mbox): (&'a Account, &'a Mbox)) -> Self {
Self {
account,

View file

@ -0,0 +1,63 @@
//! Mailbox attribute entity module.
//!
//! This module contains the definition of the mailbox attribute and its traits implementations.
pub(crate) use imap::types::NameAttribute as AttrRemote;
use serde::Serialize;
use std::{
borrow::Cow,
fmt::{self, Display},
};
/// Wraps an `imap::types::NameAttribute`.
/// See https://serde.rs/remote-derive.html.
#[derive(Debug, PartialEq, Eq, Hash, Serialize)]
#[serde(remote = "AttrRemote")]
pub enum AttrWrap<'a> {
NoInferiors,
NoSelect,
Marked,
Unmarked,
Custom(Cow<'a, str>),
}
/// Represents the mailbox attribute.
/// See https://serde.rs/remote-derive.html.
#[derive(Debug, PartialEq, Eq, Hash, Serialize)]
pub struct Attr<'a>(#[serde(with = "AttrWrap")] pub &'a AttrRemote<'a>);
/// Makes the attribute displayable.
impl<'a> Display for Attr<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self.0 {
AttrRemote::NoInferiors => write!(f, "NoInferiors"),
AttrRemote::NoSelect => write!(f, "NoSelect"),
AttrRemote::Marked => write!(f, "Marked"),
AttrRemote::Unmarked => write!(f, "Unmarked"),
AttrRemote::Custom(cow) => write!(f, "{}", cow),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_should_display_attr() {
macro_rules! attr_from {
($attr:ident) => {
Attr(&AttrRemote::$attr).to_string()
};
($custom:literal) => {
Attr(&AttrRemote::Custom($custom.into())).to_string()
};
}
assert_eq!("NoInferiors", attr_from![NoInferiors]);
assert_eq!("NoSelect", attr_from![NoSelect]);
assert_eq!("Marked", attr_from![Marked]);
assert_eq!("Unmarked", attr_from![Unmarked]);
assert_eq!("CustomAttr", attr_from!["CustomAttr"]);
}
}

View file

@ -0,0 +1,72 @@
//! Mailbox attributes entity module.
//!
//! This module contains the definition of the mailbox attributes and its traits implementations.
use serde::Serialize;
use std::{
collections::HashSet,
fmt::{self, Display},
ops::Deref,
};
use crate::domain::{Attr, AttrRemote};
/// Represents the attributes of the mailbox.
/// A HashSet is used in order to avoid duplicates.
#[derive(Debug, Default, PartialEq, Eq, Serialize)]
pub struct Attrs<'a>(HashSet<Attr<'a>>);
/// Converts a slice of `imap::types::NameAttribute` into attributes.
impl<'a> From<&'a [AttrRemote<'a>]> for Attrs<'a> {
fn from(attrs: &'a [AttrRemote<'a>]) -> Self {
Self(attrs.iter().map(Attr).collect())
}
}
/// Derefs the attributes to its inner hashset.
impl<'a> Deref for Attrs<'a> {
type Target = HashSet<Attr<'a>>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
/// Makes the attributes displayable.
impl<'a> Display for Attrs<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut glue = "";
for attr in self.iter() {
write!(f, "{}{}", glue, attr)?;
glue = ", ";
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_should_display_attrs() {
macro_rules! attrs_from {
($($attr:expr),*) => {
Attrs::from(&[$($attr,)*] as &[AttrRemote]).to_string()
};
}
let empty_attr = attrs_from![];
let single_attr = attrs_from![AttrRemote::NoInferiors];
let multiple_attrs = attrs_from![
AttrRemote::Custom("AttrCustom".into()),
AttrRemote::NoInferiors
];
assert_eq!("", empty_attr);
assert_eq!("NoInferiors", single_attr);
assert!(multiple_attrs.contains("NoInferiors"));
assert!(multiple_attrs.contains("AttrCustom"));
assert!(multiple_attrs.contains(","));
}
}

View file

@ -1,48 +1,122 @@
//! Module related to mailbox CLI.
//! Mailbox CLI module.
//!
//! This module provides subcommands, arguments and a command matcher related to mailbox.
//! This module provides subcommands, arguments and a command matcher related to the mailbox
//! domain.
use anyhow::Result;
use clap::{App, Arg, ArgMatches, SubCommand};
use log::debug;
use clap;
use log::trace;
/// Mailbox commands.
pub enum Command {
/// List all available mailboxes.
/// Represents the mailbox commands.
#[derive(Debug, PartialEq, Eq)]
pub(crate) enum Cmd {
/// Represents the list mailboxes command.
List,
}
/// Mailbox command matcher.
pub fn matches(m: &ArgMatches) -> Result<Option<Command>> {
/// Defines the mailbox command matcher.
pub(crate) fn matches(m: &clap::ArgMatches) -> Result<Option<Cmd>> {
if let Some(_) = m.subcommand_matches("mailboxes") {
debug!("mailboxes command matched");
return Ok(Some(Command::List));
trace!("mailboxes subcommand matched");
return Ok(Some(Cmd::List));
}
Ok(None)
}
/// Mailbox subcommands.
pub fn subcmds<'a>() -> Vec<App<'a, 'a>> {
vec![SubCommand::with_name("mailboxes")
/// Contains mailbox subcommands.
pub(crate) fn subcmds<'a>() -> Vec<clap::App<'a, 'a>> {
vec![clap::SubCommand::with_name("mailboxes")
.aliases(&["mailbox", "mboxes", "mbox", "mb", "m"])
.about("Lists all mailboxes")]
.about("Lists mailboxes")]
}
/// Source mailbox argument.
pub fn source_arg<'a>() -> Arg<'a, 'a> {
Arg::with_name("mailbox")
/// Defines the source mailbox argument.
pub(crate) fn source_arg<'a>() -> clap::Arg<'a, 'a> {
clap::Arg::with_name("mbox-source")
.short("m")
.long("mailbox")
.help("Selects a specific mailbox")
.value_name("MAILBOX")
.help("Specifies the source mailbox")
.value_name("SOURCE")
.default_value("INBOX")
}
/// Target mailbox argument.
pub fn target_arg<'a>() -> Arg<'a, 'a> {
Arg::with_name("target")
/// Defines the target mailbox argument.
pub(crate) fn target_arg<'a>() -> clap::Arg<'a, 'a> {
clap::Arg::with_name("mbox-target")
.help("Specifies the targetted mailbox")
.value_name("TARGET")
.required(true)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_should_match_cmds() {
let arg = clap::App::new("himalaya")
.subcommands(subcmds())
.get_matches_from(&["himalaya", "mailboxes"]);
assert_eq!(Some(Cmd::List), matches(&arg).unwrap());
}
#[test]
fn it_should_match_aliases() {
macro_rules! get_matches_from {
($alias:expr) => {
clap::App::new("himalaya")
.subcommands(subcmds())
.get_matches_from(&["himalaya", $alias])
.subcommand_name()
};
}
assert_eq!(Some("mailboxes"), get_matches_from!["mailboxes"]);
assert_eq!(Some("mailboxes"), get_matches_from!["mboxes"]);
assert_eq!(Some("mailboxes"), get_matches_from!["mbox"]);
assert_eq!(Some("mailboxes"), get_matches_from!["mb"]);
assert_eq!(Some("mailboxes"), get_matches_from!["m"]);
}
#[test]
fn it_should_match_source_arg() {
macro_rules! get_matches_from {
($($arg:expr),*) => {
clap::App::new("himalaya")
.arg(source_arg())
.get_matches_from(&["himalaya", $($arg,)*])
};
}
let app = get_matches_from![];
assert_eq!(Some("INBOX"), app.value_of("mbox-source"));
let app = get_matches_from!["-m", "SOURCE"];
assert_eq!(Some("SOURCE"), app.value_of("mbox-source"));
let app = get_matches_from!["--mailbox", "SOURCE"];
assert_eq!(Some("SOURCE"), app.value_of("mbox-source"));
}
#[test]
fn it_should_match_target_arg() {
macro_rules! get_matches_from {
($($arg:expr),*) => {
clap::App::new("himalaya")
.arg(target_arg())
.get_matches_from_safe(&["himalaya", $($arg,)*])
};
}
let app = get_matches_from![];
assert_eq!(
clap::ErrorKind::MissingRequiredArgument,
app.unwrap_err().kind
);
let app = get_matches_from!["TARGET"];
assert_eq!(Some("TARGET"), app.unwrap().value_of("mbox-target"));
}
}

View file

@ -1,155 +1,50 @@
use anyhow::{anyhow, Error, Result};
use imap::types::NameAttribute;
use log::debug;
use serde::{
ser::{self, SerializeSeq},
Serialize,
//! Mailbox entity module.
//!
//! This module contains the definition of the mailbox and its traits implementations.
use serde::Serialize;
use std::{
borrow::Cow,
fmt::{self, Display},
};
use std::{borrow::Cow, collections::HashSet, convert::TryFrom, fmt};
use crate::ui::table::{Cell, Row, Table};
use crate::{
domain::Attrs,
ui::{Cell, Row, Table},
};
// Attribute
/// Represents the mailbox.
#[derive(Debug, Default, PartialEq, Eq, Serialize)]
pub struct Mbox<'a> {
/// Represents the mailbox hierarchie delimiter.
pub delim: Cow<'a, str>,
#[derive(Debug, PartialEq)]
struct SerializableAttribute<'a>(&'a NameAttribute<'a>);
/// Represents the mailbox name.
pub name: Cow<'a, str>,
impl<'a> Into<&'a str> for &'a SerializableAttribute<'a> {
fn into(self) -> &'a str {
match &self.0 {
NameAttribute::NoInferiors => "\\NoInferiors",
NameAttribute::NoSelect => "\\NoSelect",
NameAttribute::Marked => "\\Marked",
NameAttribute::Unmarked => "\\Unmarked",
NameAttribute::Custom(cow) => cow,
}
}
/// Represents the mailbox attributes.
pub attrs: Attrs<'a>,
}
impl<'a> ser::Serialize for SerializableAttribute<'a> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
{
serializer.serialize_str(self.into())
}
}
/// Represents the attributes of a mailbox.
#[derive(Debug, PartialEq)]
pub struct Attributes(pub HashSet<NameAttribute<'static>>);
impl<'a> From<&[NameAttribute<'a>]> for Attributes {
fn from(attrs: &[NameAttribute<'a>]) -> Self {
Self(
attrs
.iter()
.map(|attribute| convert_to_static(attribute).unwrap())
.collect::<HashSet<NameAttribute<'static>>>(),
)
}
}
impl ToString for Attributes {
fn to_string(&self) -> String {
let mut attributes = String::new();
for attribute in &self.0 {
let attribute = SerializableAttribute(&attribute);
attributes.push_str((&attribute).into());
attributes.push_str(", ");
}
// remove the trailing whitespace with the comma
attributes = attributes.trim_end_matches(' ').to_string();
attributes.pop();
attributes
}
}
impl ser::Serialize for Attributes {
fn serialize<T>(&self, serializer: T) -> Result<T::Ok, T::Error>
where
T: ser::Serializer,
{
let mut seq = serializer.serialize_seq(Some(self.0.len()))?;
for attr in &self.0 {
seq.serialize_element(&SerializableAttribute(attr))?;
}
seq.end()
}
}
// --- Mailbox ---
/// Represents a general mailbox.
#[derive(Debug, Serialize)]
pub struct Mbox {
/// The [hierarchie delimiter].
///
/// [hierarchie delimiter]: https://docs.rs/imap/2.4.1/imap/types/struct.Name.html#method.delimiter
pub delim: String,
/// The name of the mailbox.
pub name: String,
/// Its attributes.
pub attributes: Attributes,
}
impl Default for Mbox {
fn default() -> Self {
impl<'a> Mbox<'a> {
/// Creates a new mailbox with just a name.
pub fn new(name: &'a str) -> Self {
Self {
delim: String::default(),
name: String::default(),
attributes: Attributes::from(&[] as &[NameAttribute]),
name: name.into(),
..Self::default()
}
}
}
impl fmt::Display for Mbox {
/// Makes the mailbox displayable.
impl<'a> Display for Mbox<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.name)
}
}
impl From<&str> for Mbox {
fn from(mbox: &str) -> Self {
debug!("init mailbox from `{:?}`", mbox);
Self {
name: mbox.to_owned(),
..Self::default()
}
}
}
impl<'a> From<&'a imap::types::Name> for Mbox {
fn from(name: &'a imap::types::Name) -> Self {
Self {
delim: name.delimiter().unwrap_or_default().to_owned(),
name: name.name().to_owned(),
attributes: Attributes::from(name.attributes()),
}
}
}
impl TryFrom<Option<&str>> for Mbox {
type Error = Error;
fn try_from(mbox: Option<&str>) -> Result<Self, Self::Error> {
debug!("init mailbox from `{:?}`", mbox);
Ok(Self {
name: mbox
.ok_or(anyhow!("the target mailbox cannot be empty"))?
.to_string(),
..Self::default()
})
}
}
impl Table for Mbox {
/// Makes the mailbox tableable.
impl<'a> Table for Mbox<'a> {
fn head() -> Row {
Row::new()
.cell(Cell::new("DELIM").bold().underline().white())
@ -167,36 +62,52 @@ impl Table for Mbox {
Row::new()
.cell(Cell::new(&self.delim).white())
.cell(Cell::new(&self.name).green())
.cell(Cell::new(&self.attributes.to_string()).shrinkable().blue())
.cell(Cell::new(&self.attrs.to_string()).shrinkable().blue())
}
}
// --- Mboxes ---
/// A simple wrapper to acces a bunch of mboxes which are in this vector.
#[derive(Debug, Serialize)]
pub struct Mboxes(pub Vec<Mbox>);
impl<'a> From<&'a imap::types::ZeroCopy<Vec<imap::types::Name>>> for Mboxes {
fn from(names: &'a imap::types::ZeroCopy<Vec<imap::types::Name>>) -> Self {
Self(names.iter().map(Mbox::from).collect::<Vec<_>>())
/// Converts an `imap::types::Name` into a mailbox.
impl<'a> From<&'a imap::types::Name> for Mbox<'a> {
fn from(name: &'a imap::types::Name) -> Self {
Self {
delim: name.delimiter().unwrap_or_default().into(),
name: name.name().into(),
attrs: Attrs::from(name.attributes()),
}
}
}
impl fmt::Display for Mboxes {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f, "\n{}", Table::render(&self.0))
}
}
#[cfg(test)]
mod tests {
use super::super::AttrRemote;
use super::*;
// == Helper Functions ==
fn convert_to_static<'func>(
attribute: &'func NameAttribute<'func>,
) -> Result<NameAttribute<'static>, ()> {
match attribute {
NameAttribute::NoInferiors => Ok(NameAttribute::NoInferiors),
NameAttribute::NoSelect => Ok(NameAttribute::NoSelect),
NameAttribute::Marked => Ok(NameAttribute::Marked),
NameAttribute::Unmarked => Ok(NameAttribute::Unmarked),
NameAttribute::Custom(cow) => Ok(NameAttribute::Custom(Cow::Owned(cow.to_string()))),
#[test]
fn it_should_create_new_mbox() {
assert_eq!(Mbox::default(), Mbox::new(""));
assert_eq!(
Mbox {
delim: Cow::default(),
name: "INBOX".into(),
attrs: Attrs::default()
},
Mbox::new("INBOX")
);
}
#[test]
fn it_should_display_mbox() {
let default_mbox = Mbox::default();
assert_eq!("", default_mbox.to_string());
let new_mbox = Mbox::new("INBOX");
assert_eq!("INBOX", new_mbox.to_string());
let full_mbox = Mbox {
delim: ".".into(),
name: "Sent".into(),
attrs: Attrs::from(&[AttrRemote::NoSelect] as &[AttrRemote]),
};
assert_eq!("Sent", full_mbox.to_string());
}
}

View file

@ -1,24 +1,22 @@
//! Module related to mailboxes handling.
//! Mailbox handling module.
//!
//! This module gathers all mailboxes actions triggered by the CLI.
//! This module gathers all mailbox actions triggered by the CLI.
use anyhow::Result;
use log::{debug, trace};
use log::trace;
use crate::{
domain::{imap::ImapServiceInterface, mbox::Mboxes},
output::{OutputService, OutputServiceInterface},
domain::{ImapServiceInterface, Mboxes},
output::OutputServiceInterface,
};
/// List all mailboxes.
pub fn list<ImapService: ImapServiceInterface>(
pub fn list<OutputService: OutputServiceInterface, ImapService: ImapServiceInterface>(
output: &OutputService,
imap: &mut ImapService,
) -> Result<()> {
let names = imap.get_mboxes()?;
let mboxes = Mboxes::from(&names);
debug!("mailboxes len: {}", mboxes.0.len());
let raw_mboxes = imap.fetch_raw_mboxes()?;
let mboxes = Mboxes::from(&raw_mboxes);
trace!("mailboxes: {:#?}", mboxes);
output.print(mboxes)?;
Ok(())
output.print(mboxes)
}

View file

@ -0,0 +1,38 @@
//! Mailboxes entity module.
//!
//! This module contains the definition of the mailboxes and its traits implementations.
use serde::Serialize;
use std::{
fmt::{self, Display},
ops::Deref,
};
use crate::{domain::Mbox, ui::Table};
/// Represents a list of mailboxes.
#[derive(Debug, Serialize)]
pub struct Mboxes<'a>(pub Vec<Mbox<'a>>);
/// Derefs the mailboxes to its inner vector.
impl<'a> Deref for Mboxes<'a> {
type Target = Vec<Mbox<'a>>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
/// Makes the mailboxes displayable.
impl<'a> Display for Mboxes<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f, "\n{}", Table::render(&self))
}
}
/// Converts a list of `imap::types::Name` into mailboxes.
impl<'a> From<&'a imap::types::ZeroCopy<Vec<imap::types::Name>>> for Mboxes<'a> {
fn from(names: &'a imap::types::ZeroCopy<Vec<imap::types::Name>>) -> Self {
Self(names.iter().map(Mbox::from).collect())
}
}

View file

@ -1,7 +1,18 @@
//! Module related to mailbox.
//! Mailbox module.
//!
//! This module contains everything related to mailbox.
pub mod mbox_arg;
pub mod mbox_handler;
pub mod attr_entity;
pub use attr_entity::*;
pub mod attrs_entity;
pub use attrs_entity::*;
pub mod mbox_entity;
pub use mbox_entity::*;
pub mod mboxes_entity;
pub use mboxes_entity::*;

View file

@ -14,7 +14,7 @@ use crate::domain::{
type Seq<'a> = &'a str;
type PageSize = usize;
type Page = usize;
type Mbox<'a> = Option<&'a str>;
type Mbox<'a> = &'a str;
type TextMime<'a> = &'a str;
type Raw = bool;
type All = bool;
@ -54,9 +54,9 @@ pub fn matches<'a>(m: &'a ArgMatches) -> Result<Option<Command<'a>>> {
debug!("copy command matched");
let seq = m.value_of("seq").unwrap();
trace!("seq: {}", seq);
let target = m.value_of("target");
trace!(r#"target mailbox: "{:?}""#, target);
return Ok(Some(Command::Copy(seq, target)));
let mbox = m.value_of("mbox-target").unwrap();
trace!(r#"target mailbox: "{:?}""#, mbox);
return Ok(Some(Command::Copy(seq, mbox)));
}
if let Some(m) = m.subcommand_matches("delete") {
@ -94,9 +94,9 @@ pub fn matches<'a>(m: &'a ArgMatches) -> Result<Option<Command<'a>>> {
debug!("move command matched");
let seq = m.value_of("seq").unwrap();
trace!("seq: {}", seq);
let target = m.value_of("target");
trace!(r#"target mailbox: "{:?}""#, target);
return Ok(Some(Command::Move(seq, target)));
let mbox = m.value_of("mbox-target").unwrap();
trace!(r#"target mailbox: "{:?}""#, mbox);
return Ok(Some(Command::Move(seq, mbox)));
}
if let Some(m) = m.subcommand_matches("read") {
@ -125,9 +125,9 @@ pub fn matches<'a>(m: &'a ArgMatches) -> Result<Option<Command<'a>>> {
debug!("save command matched");
let msg = m.value_of("message").unwrap();
debug!("message: {}", &msg);
let target = m.value_of("target");
debug!("target mailbox: `{:?}`", target);
return Ok(Some(Command::Save(target, msg)));
let mbox = m.value_of("mbox-target").unwrap();
debug!("target mailbox: `{:?}`", mbox);
return Ok(Some(Command::Save(mbox, msg)));
}
if let Some(m) = m.subcommand_matches("search") {

View file

@ -335,7 +335,7 @@ impl Msg {
loop {
match choice::post_edit() {
Ok(PostEditChoice::Send) => {
let mbox = Mbox::from("Sent");
let mbox = Mbox::new("Sent");
let sent_msg = smtp.send_msg(&self)?;
let flags = Flags::try_from(vec![Flag::Seen])?;
imap.append_raw_msg_with_flags(&mbox, &sent_msg.formatted(), flags)?;
@ -352,7 +352,7 @@ impl Msg {
break;
}
Ok(PostEditChoice::RemoteDraft) => {
let mbox = Mbox::from("Drafts");
let mbox = Mbox::new("Drafts");
let flags = Flags::try_from(vec![Flag::Seen, Flag::Draft])?;
let tpl = Tpl::from_msg(TplOverride::default(), &self, account);
imap.append_raw_msg_with_flags(&mbox, tpl.as_bytes(), flags)?;

View file

@ -25,8 +25,7 @@ use crate::{
output::OutputServiceInterface,
};
/// Download all attachments from the given message sequence number to the user account downloads
/// directory.
/// Download all message attachments to the user account downloads directory.
pub fn attachments<OutputService: OutputServiceInterface, ImapService: ImapServiceInterface>(
seq: &str,
account: &Account,
@ -56,11 +55,11 @@ pub fn attachments<OutputService: OutputServiceInterface, ImapService: ImapServi
/// Copy a message from a mailbox to another.
pub fn copy<OutputService: OutputServiceInterface, ImapService: ImapServiceInterface>(
seq: &str,
mbox: Option<&str>,
mbox: &str,
output: &OutputService,
imap: &mut ImapService,
) -> Result<()> {
let mbox = Mbox::try_from(mbox)?;
let mbox = Mbox::new(mbox);
let msg = imap.find_raw_msg(&seq)?;
let flags = Flags::try_from(vec![Flag::Seen])?;
imap.append_raw_msg_with_flags(&mbox, &msg, flags)?;
@ -177,12 +176,12 @@ pub fn move_<OutputService: OutputServiceInterface, ImapService: ImapServiceInte
// The sequence number of the message to move
seq: &str,
// The mailbox to move the message in
mbox: Option<&str>,
mbox: &str,
output: &OutputService,
imap: &mut ImapService,
) -> Result<()> {
// Copy the message to targetted mailbox
let mbox = Mbox::try_from(mbox)?;
let mbox = Mbox::new(mbox);
let msg = imap.find_raw_msg(&seq)?;
let flags = Flags::try_from(vec![Flag::Seen])?;
imap.append_raw_msg_with_flags(&mbox, &msg, flags)?;
@ -239,11 +238,11 @@ pub fn reply<
/// Save a raw message to the targetted mailbox.
pub fn save<ImapService: ImapServiceInterface>(
mbox: Option<&str>,
mbox: &str,
msg: &str,
imap: &mut ImapService,
) -> Result<()> {
let mbox = Mbox::try_from(mbox)?;
let mbox = Mbox::new(mbox);
let flags = Flags::try_from(vec![Flag::Seen])?;
imap.append_raw_msg_with_flags(&mbox, msg.as_bytes(), flags)
}
@ -295,7 +294,7 @@ pub fn send<
debug!("message sent!");
// Save message to sent folder
let mbox = Mbox::from("Sent");
let mbox = Mbox::new("Sent");
let flags = Flags::try_from(vec![Flag::Seen])?;
imap.append_raw_msg_with_flags(&mbox, raw_msg.as_bytes(), flags)
}

View file

@ -1,5 +1,5 @@
use anyhow::Result;
use clap::{self, AppSettings};
use clap;
use env_logger;
use std::{convert::TryFrom, env};
use url::Url;
@ -10,25 +10,26 @@ mod domain;
mod output;
mod ui;
use config::{Account, Config};
use compl::{compl_arg, compl_handler};
use config::{config_arg, Account, Config};
use domain::{
imap::{imap_arg, imap_handler, ImapService, ImapServiceInterface},
mbox::{mbox_arg, mbox_handler, Mbox},
msg::{flag_arg, flag_handler, msg_arg, msg_handler, tpl_arg, tpl_handler},
smtp::SmtpService,
};
use output::OutputService;
use output::{output_arg, OutputService};
fn create_app<'a>() -> clap::App<'a, 'a> {
clap::App::new(env!("CARGO_PKG_NAME"))
.version(env!("CARGO_PKG_VERSION"))
.about(env!("CARGO_PKG_DESCRIPTION"))
.author(env!("CARGO_PKG_AUTHORS"))
.setting(AppSettings::GlobalVersion)
.args(&config::config_arg::args())
.args(&output::output_arg::args())
.global_setting(clap::AppSettings::GlobalVersion)
.args(&config_arg::args())
.args(&output_arg::args())
.arg(mbox_arg::source_arg())
.subcommands(compl::compl_arg::subcmds())
.subcommands(compl_arg::subcmds())
.subcommands(imap_arg::subcmds())
.subcommands(mbox_arg::subcmds())
.subcommands(msg_arg::subcmds())
@ -40,10 +41,10 @@ fn main() -> Result<()> {
env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "off"),
);
// Check mailto match BEFORE app initialization.
// Check mailto command 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 mbox = Mbox::new("INBOX");
let config = Config::try_from(None)?;
let account = Account::try_from((&config, None))?;
let output = OutputService::from("plain");
@ -56,23 +57,24 @@ fn main() -> Result<()> {
let app = create_app();
let m = app.get_matches();
// Check completion match BEFORE entities and services initialization.
// Linked issue: https://github.com/soywod/himalaya/issues/115.
match compl::compl_arg::matches(&m)? {
Some(compl::compl_arg::Command::Generate(shell)) => {
return compl::compl_handler::generate(create_app(), shell);
// Check completion command BEFORE entities and services initialization.
// Related issue: https://github.com/soywod/himalaya/issues/115.
match compl_arg::matches(&m)? {
Some(compl_arg::Command::Generate(shell)) => {
return compl_handler::generate(create_app(), shell);
}
_ => (),
}
let mbox = Mbox::try_from(m.value_of("mailbox"))?;
// Init entities and services.
let mbox = Mbox::new(m.value_of("mbox-source").unwrap());
let config = Config::try_from(m.value_of("config"))?;
let account = Account::try_from((&config, m.value_of("account")))?;
let output = OutputService::try_from(m.value_of("output"))?;
let mut imap = ImapService::from((&account, &mbox));
let mut smtp = SmtpService::from(&account);
// Check IMAP matches.
// Check IMAP commands.
match imap_arg::matches(&m)? {
Some(imap_arg::Command::Notify(keepalive)) => {
return imap_handler::notify(keepalive, &config, &mut imap);
@ -83,21 +85,21 @@ fn main() -> Result<()> {
_ => (),
}
// Check mailbox matches.
// Check mailbox commands.
match mbox_arg::matches(&m)? {
Some(mbox_arg::Command::List) => {
Some(mbox_arg::Cmd::List) => {
return mbox_handler::list(&output, &mut imap);
}
_ => (),
}
// Check message matches.
// Check message commands.
match msg_arg::matches(&m)? {
Some(msg_arg::Command::Attachments(seq)) => {
return msg_handler::attachments(seq, &account, &output, &mut imap);
}
Some(msg_arg::Command::Copy(seq, target)) => {
return msg_handler::copy(seq, target, &output, &mut imap);
Some(msg_arg::Command::Copy(seq, mbox)) => {
return msg_handler::copy(seq, mbox, &output, &mut imap);
}
Some(msg_arg::Command::Delete(seq)) => {
return msg_handler::delete(seq, &output, &mut imap);
@ -108,8 +110,8 @@ fn main() -> Result<()> {
Some(msg_arg::Command::List(page_size, page)) => {
return msg_handler::list(page_size, page, &account, &output, &mut imap);
}
Some(msg_arg::Command::Move(seq, target)) => {
return msg_handler::move_(seq, target, &output, &mut imap);
Some(msg_arg::Command::Move(seq, mbox)) => {
return msg_handler::move_(seq, mbox, &output, &mut imap);
}
Some(msg_arg::Command::Read(seq, text_mime, raw)) => {
return msg_handler::read(seq, text_mime, raw, &output, &mut imap);
@ -117,8 +119,8 @@ fn main() -> Result<()> {
Some(msg_arg::Command::Reply(seq, all, atts)) => {
return msg_handler::reply(seq, all, atts, &account, &output, &mut imap, &mut smtp);
}
Some(msg_arg::Command::Save(target, msg)) => {
return msg_handler::save(target, msg, &mut imap);
Some(msg_arg::Command::Save(mbox, msg)) => {
return msg_handler::save(mbox, msg, &mut imap);
}
Some(msg_arg::Command::Search(query, page_size, page)) => {
return msg_handler::search(query, page_size, page, &account, &output, &mut imap);