mirror of
https://github.com/soywod/himalaya.git
synced 2024-07-05 17:15:12 +00:00
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:
parent
d21778c35e
commit
8af14734c3
|
@ -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,
|
||||
|
|
63
src/domain/mbox/attr_entity.rs
Normal file
63
src/domain/mbox/attr_entity.rs
Normal 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"]);
|
||||
}
|
||||
}
|
72
src/domain/mbox/attrs_entity.rs
Normal file
72
src/domain/mbox/attrs_entity.rs
Normal 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(","));
|
||||
}
|
||||
}
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
38
src/domain/mbox/mboxes_entity.rs
Normal file
38
src/domain/mbox/mboxes_entity.rs
Normal 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())
|
||||
}
|
||||
}
|
|
@ -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::*;
|
||||
|
|
|
@ -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") {
|
||||
|
|
|
@ -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)?;
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
52
src/main.rs
52
src/main.rs
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue