mirror of
https://github.com/soywod/himalaya.git
synced 2024-07-08 18:45:13 +00:00
move envelopes and flags to lib
refactor maildir envelopes/flags refactor notmuch envelopes
This commit is contained in:
parent
ca67780341
commit
8f667def0c
|
@ -20,7 +20,7 @@ section = "mail"
|
|||
imap-backend = ["imap", "imap-proto"]
|
||||
maildir-backend = ["maildir", "md5"]
|
||||
notmuch-backend = ["notmuch", "maildir-backend"]
|
||||
default = ["imap-backend", "maildir-backend", "notmuch-backend"]
|
||||
default = ["imap-backend", "maildir-backend"]
|
||||
|
||||
[dependencies]
|
||||
ammonia = "3.1.2"
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
//! custom backend implementations.
|
||||
|
||||
use anyhow::Result;
|
||||
use himalaya_lib::mbox::Mboxes;
|
||||
use himalaya_lib::{mbox::Mboxes, msg::Envelopes};
|
||||
|
||||
use crate::msg::{Envelopes, Msg};
|
||||
use crate::msg::Msg;
|
||||
|
||||
pub trait Backend<'a> {
|
||||
fn connect(&mut self) -> Result<()> {
|
||||
|
@ -16,12 +16,7 @@ pub trait Backend<'a> {
|
|||
fn add_mbox(&mut self, mbox: &str) -> Result<()>;
|
||||
fn get_mboxes(&mut self) -> Result<Mboxes>;
|
||||
fn del_mbox(&mut self, mbox: &str) -> Result<()>;
|
||||
fn get_envelopes(
|
||||
&mut self,
|
||||
mbox: &str,
|
||||
page_size: usize,
|
||||
page: usize,
|
||||
) -> Result<Box<dyn Envelopes>>;
|
||||
fn get_envelopes(&mut self, mbox: &str, page_size: usize, page: usize) -> Result<Envelopes>;
|
||||
fn search_envelopes(
|
||||
&mut self,
|
||||
mbox: &str,
|
||||
|
@ -29,7 +24,7 @@ pub trait Backend<'a> {
|
|||
sort: &str,
|
||||
page_size: usize,
|
||||
page: usize,
|
||||
) -> Result<Box<dyn Envelopes>>;
|
||||
) -> Result<Envelopes>;
|
||||
fn add_msg(&mut self, mbox: &str, msg: &[u8], flags: &str) -> Result<String>;
|
||||
fn get_msg(&mut self, mbox: &str, id: &str) -> Result<Msg>;
|
||||
fn copy_msg(&mut self, mbox_src: &str, mbox_dst: &str, ids: &str) -> Result<()>;
|
||||
|
|
|
@ -6,25 +6,22 @@ use anyhow::{anyhow, Context, Result};
|
|||
use himalaya_lib::{
|
||||
account::{AccountConfig, ImapBackendConfig},
|
||||
mbox::{Mbox, Mboxes},
|
||||
msg::{Envelopes, Flags},
|
||||
};
|
||||
use imap::types::NameAttribute;
|
||||
use log::{debug, log_enabled, trace, Level};
|
||||
use native_tls::{TlsConnector, TlsStream};
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
convert::{TryFrom, TryInto},
|
||||
net::TcpStream,
|
||||
thread,
|
||||
};
|
||||
use std::{collections::HashSet, convert::TryInto, net::TcpStream, thread};
|
||||
|
||||
use crate::{
|
||||
backends::{imap::msg_sort_criterion::SortCriteria, Backend, ImapEnvelope, ImapEnvelopes},
|
||||
msg::{Envelopes, Msg},
|
||||
backends::{
|
||||
from_imap_fetch, from_imap_fetches, imap::msg_sort_criterion::SortCriteria,
|
||||
into_imap_flags, Backend,
|
||||
},
|
||||
msg::Msg,
|
||||
output::run_cmd,
|
||||
};
|
||||
|
||||
use super::ImapFlags;
|
||||
|
||||
type ImapSess = imap::Session<TlsStream<TcpStream>>;
|
||||
|
||||
pub struct ImapBackend<'a> {
|
||||
|
@ -148,7 +145,7 @@ impl<'a> ImapBackend<'a> {
|
|||
.context("cannot fetch new messages enveloppe")?;
|
||||
|
||||
for fetch in fetches.iter() {
|
||||
let msg = ImapEnvelope::try_from(fetch)?;
|
||||
let msg = from_imap_fetch(fetch)?;
|
||||
let uid = fetch.uid.ok_or_else(|| {
|
||||
anyhow!("cannot retrieve message {}'s UID", fetch.message)
|
||||
})?;
|
||||
|
@ -252,12 +249,7 @@ impl<'a> Backend<'a> for ImapBackend<'a> {
|
|||
.context(format!("cannot delete imap mailbox {:?}", mbox))
|
||||
}
|
||||
|
||||
fn get_envelopes(
|
||||
&mut self,
|
||||
mbox: &str,
|
||||
page_size: usize,
|
||||
page: usize,
|
||||
) -> Result<Box<dyn Envelopes>> {
|
||||
fn get_envelopes(&mut self, mbox: &str, page_size: usize, page: usize) -> Result<Envelopes> {
|
||||
let last_seq = self
|
||||
.sess()?
|
||||
.select(mbox)
|
||||
|
@ -265,7 +257,7 @@ impl<'a> Backend<'a> for ImapBackend<'a> {
|
|||
.exists as usize;
|
||||
debug!("last sequence number: {:?}", last_seq);
|
||||
if last_seq == 0 {
|
||||
return Ok(Box::new(ImapEnvelopes::default()));
|
||||
return Ok(Envelopes::default());
|
||||
}
|
||||
|
||||
let range = if page_size > 0 {
|
||||
|
@ -282,8 +274,8 @@ impl<'a> Backend<'a> for ImapBackend<'a> {
|
|||
.sess()?
|
||||
.fetch(&range, "(ENVELOPE FLAGS INTERNALDATE)")
|
||||
.context(format!("cannot fetch messages within range {:?}", range))?;
|
||||
let envelopes: ImapEnvelopes = fetches.try_into()?;
|
||||
Ok(Box::new(envelopes))
|
||||
|
||||
from_imap_fetches(fetches)
|
||||
}
|
||||
|
||||
fn search_envelopes(
|
||||
|
@ -293,7 +285,7 @@ impl<'a> Backend<'a> for ImapBackend<'a> {
|
|||
sort: &str,
|
||||
page_size: usize,
|
||||
page: usize,
|
||||
) -> Result<Box<dyn Envelopes>> {
|
||||
) -> Result<Envelopes> {
|
||||
let last_seq = self
|
||||
.sess()?
|
||||
.select(mbox)
|
||||
|
@ -301,7 +293,7 @@ impl<'a> Backend<'a> for ImapBackend<'a> {
|
|||
.exists;
|
||||
debug!("last sequence number: {:?}", last_seq);
|
||||
if last_seq == 0 {
|
||||
return Ok(Box::new(ImapEnvelopes::default()));
|
||||
return Ok(Envelopes::default());
|
||||
}
|
||||
|
||||
let begin = page * page_size;
|
||||
|
@ -330,7 +322,7 @@ impl<'a> Backend<'a> for ImapBackend<'a> {
|
|||
.collect()
|
||||
};
|
||||
if seqs.is_empty() {
|
||||
return Ok(Box::new(ImapEnvelopes::default()));
|
||||
return Ok(Envelopes::default());
|
||||
}
|
||||
|
||||
let range = seqs[begin..end.min(seqs.len())].join(",");
|
||||
|
@ -338,15 +330,15 @@ impl<'a> Backend<'a> for ImapBackend<'a> {
|
|||
.sess()?
|
||||
.fetch(&range, "(ENVELOPE FLAGS INTERNALDATE)")
|
||||
.context(format!("cannot fetch messages within range {:?}", range))?;
|
||||
let envelopes: ImapEnvelopes = fetches.try_into()?;
|
||||
Ok(Box::new(envelopes))
|
||||
|
||||
from_imap_fetches(fetches)
|
||||
}
|
||||
|
||||
fn add_msg(&mut self, mbox: &str, msg: &[u8], flags: &str) -> Result<String> {
|
||||
let flags: ImapFlags = flags.into();
|
||||
let flags: Flags = flags.into();
|
||||
self.sess()?
|
||||
.append(mbox, msg)
|
||||
.flags(<ImapFlags as Into<Vec<imap::types::Flag<'a>>>>::into(flags))
|
||||
.flags(into_imap_flags(&flags))
|
||||
.finish()
|
||||
.context(format!("cannot append message to {:?}", mbox))?;
|
||||
let last_seq = self
|
||||
|
@ -396,7 +388,7 @@ impl<'a> Backend<'a> for ImapBackend<'a> {
|
|||
}
|
||||
|
||||
fn add_flags(&mut self, mbox: &str, seq_range: &str, flags: &str) -> Result<()> {
|
||||
let flags: ImapFlags = flags.into();
|
||||
let flags: Flags = flags.into();
|
||||
self.sess()?
|
||||
.select(mbox)
|
||||
.context(format!("cannot select mailbox {:?}", mbox))?;
|
||||
|
@ -410,7 +402,7 @@ impl<'a> Backend<'a> for ImapBackend<'a> {
|
|||
}
|
||||
|
||||
fn set_flags(&mut self, mbox: &str, seq_range: &str, flags: &str) -> Result<()> {
|
||||
let flags: ImapFlags = flags.into();
|
||||
let flags: Flags = flags.into();
|
||||
self.sess()?
|
||||
.select(mbox)
|
||||
.context(format!("cannot select mailbox {:?}", mbox))?;
|
||||
|
@ -421,7 +413,7 @@ impl<'a> Backend<'a> for ImapBackend<'a> {
|
|||
}
|
||||
|
||||
fn del_flags(&mut self, mbox: &str, seq_range: &str, flags: &str) -> Result<()> {
|
||||
let flags: ImapFlags = flags.into();
|
||||
let flags: Flags = flags.into();
|
||||
self.sess()?
|
||||
.select(mbox)
|
||||
.context(format!("cannot select mailbox {:?}", mbox))?;
|
||||
|
|
|
@ -3,127 +3,23 @@
|
|||
//! This module provides IMAP types and conversion utilities related
|
||||
//! to the envelope.
|
||||
|
||||
use anyhow::{anyhow, Context, Error, Result};
|
||||
use std::{convert::TryFrom, ops::Deref};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use himalaya_lib::msg::Envelope;
|
||||
|
||||
use crate::{
|
||||
output::{PrintTable, PrintTableOpts, WriteColor},
|
||||
ui::{Cell, Row, Table},
|
||||
};
|
||||
|
||||
use super::{ImapFlag, ImapFlags};
|
||||
|
||||
/// Represents a list of IMAP envelopes.
|
||||
#[derive(Debug, Default, serde::Serialize)]
|
||||
pub struct ImapEnvelopes {
|
||||
#[serde(rename = "response")]
|
||||
pub envelopes: Vec<ImapEnvelope>,
|
||||
}
|
||||
|
||||
impl Deref for ImapEnvelopes {
|
||||
type Target = Vec<ImapEnvelope>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.envelopes
|
||||
}
|
||||
}
|
||||
|
||||
impl PrintTable for ImapEnvelopes {
|
||||
fn print_table(&self, writer: &mut dyn WriteColor, opts: PrintTableOpts) -> Result<()> {
|
||||
writeln!(writer)?;
|
||||
Table::print(writer, self, opts)?;
|
||||
writeln!(writer)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// impl Envelopes for ImapEnvelopes {
|
||||
// //
|
||||
// }
|
||||
|
||||
/// Represents the IMAP envelope. The envelope is just a message
|
||||
/// subset, and is mostly used for listings.
|
||||
#[derive(Debug, Default, Clone, serde::Serialize)]
|
||||
pub struct ImapEnvelope {
|
||||
/// Represents the sequence number of the message.
|
||||
///
|
||||
/// [RFC3501]: https://datatracker.ietf.org/doc/html/rfc3501#section-2.3.1.2
|
||||
pub id: u32,
|
||||
|
||||
/// Represents the flags attached to the message.
|
||||
pub flags: ImapFlags,
|
||||
|
||||
/// Represents the subject of the message.
|
||||
pub subject: String,
|
||||
|
||||
/// Represents the first sender of the message.
|
||||
pub sender: String,
|
||||
|
||||
/// Represents the internal date of the message.
|
||||
///
|
||||
/// [RFC3501]: https://datatracker.ietf.org/doc/html/rfc3501#section-2.3.3
|
||||
pub date: Option<String>,
|
||||
}
|
||||
|
||||
impl Table for ImapEnvelope {
|
||||
fn head() -> Row {
|
||||
Row::new()
|
||||
.cell(Cell::new("ID").bold().underline().white())
|
||||
.cell(Cell::new("FLAGS").bold().underline().white())
|
||||
.cell(Cell::new("SUBJECT").shrinkable().bold().underline().white())
|
||||
.cell(Cell::new("SENDER").bold().underline().white())
|
||||
.cell(Cell::new("DATE").bold().underline().white())
|
||||
}
|
||||
|
||||
fn row(&self) -> Row {
|
||||
let id = self.id.to_string();
|
||||
let flags = self.flags.to_symbols_string();
|
||||
let unseen = !self.flags.contains(&ImapFlag::Seen);
|
||||
let subject = &self.subject;
|
||||
let sender = &self.sender;
|
||||
let date = self.date.as_deref().unwrap_or_default();
|
||||
Row::new()
|
||||
.cell(Cell::new(id).bold_if(unseen).red())
|
||||
.cell(Cell::new(flags).bold_if(unseen).white())
|
||||
.cell(Cell::new(subject).shrinkable().bold_if(unseen).green())
|
||||
.cell(Cell::new(sender).bold_if(unseen).blue())
|
||||
.cell(Cell::new(date).bold_if(unseen).yellow())
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a list of raw envelopes returned by the `imap` crate.
|
||||
pub type RawImapEnvelopes = imap::types::ZeroCopy<Vec<RawImapEnvelope>>;
|
||||
|
||||
impl TryFrom<RawImapEnvelopes> for ImapEnvelopes {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(raw_envelopes: RawImapEnvelopes) -> Result<Self, Self::Error> {
|
||||
let mut envelopes = vec![];
|
||||
for raw_envelope in raw_envelopes.iter().rev() {
|
||||
envelopes.push(ImapEnvelope::try_from(raw_envelope).context("cannot parse envelope")?);
|
||||
}
|
||||
Ok(Self { envelopes })
|
||||
}
|
||||
}
|
||||
use super::from_imap_flags;
|
||||
|
||||
/// Represents the raw envelope returned by the `imap` crate.
|
||||
pub type RawImapEnvelope = imap::types::Fetch;
|
||||
pub type ImapFetch = imap::types::Fetch;
|
||||
|
||||
impl TryFrom<&RawImapEnvelope> for ImapEnvelope {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(fetch: &RawImapEnvelope) -> Result<ImapEnvelope> {
|
||||
pub fn from_imap_fetch(fetch: &ImapFetch) -> Result<Envelope> {
|
||||
let envelope = fetch
|
||||
.envelope()
|
||||
.ok_or_else(|| anyhow!("cannot get envelope of message {}", fetch.message))?;
|
||||
|
||||
// Get the sequence number
|
||||
let id = fetch.message;
|
||||
let id = fetch.message.to_string();
|
||||
|
||||
// Get the flags
|
||||
let flags = ImapFlags::try_from(fetch.flags())?;
|
||||
let flags = from_imap_flags(fetch.flags());
|
||||
|
||||
// Get the subject
|
||||
let subject = envelope
|
||||
.subject
|
||||
.as_ref()
|
||||
|
@ -135,7 +31,6 @@ impl TryFrom<&RawImapEnvelope> for ImapEnvelope {
|
|||
})
|
||||
.unwrap_or_else(|| Ok(String::default()))?;
|
||||
|
||||
// Get the sender
|
||||
let sender = envelope
|
||||
.sender
|
||||
.as_ref()
|
||||
|
@ -171,17 +66,16 @@ impl TryFrom<&RawImapEnvelope> for ImapEnvelope {
|
|||
format!("{}@{}", mbox, host)
|
||||
};
|
||||
|
||||
// Get the internal date
|
||||
let date = fetch
|
||||
.internal_date()
|
||||
.map(|date| date.naive_local().to_string());
|
||||
|
||||
Ok(Self {
|
||||
id,
|
||||
Ok(Envelope {
|
||||
id: id.clone(),
|
||||
internal_id: id,
|
||||
flags,
|
||||
subject,
|
||||
sender,
|
||||
date,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
15
cli/src/backends/imap/imap_envelopes.rs
Normal file
15
cli/src/backends/imap/imap_envelopes.rs
Normal file
|
@ -0,0 +1,15 @@
|
|||
use anyhow::{Context, Result};
|
||||
use himalaya_lib::msg::Envelopes;
|
||||
|
||||
use crate::backends::{imap::from_imap_fetch, ImapFetch};
|
||||
|
||||
/// Represents the list of raw envelopes returned by the `imap` crate.
|
||||
pub type ImapFetches = imap::types::ZeroCopy<Vec<ImapFetch>>;
|
||||
|
||||
pub fn from_imap_fetches(fetches: ImapFetches) -> Result<Envelopes> {
|
||||
let mut envelopes = Envelopes::default();
|
||||
for fetch in fetches.iter().rev() {
|
||||
envelopes.push(from_imap_fetch(fetch).context("cannot parse imap fetch")?);
|
||||
}
|
||||
Ok(envelopes)
|
||||
}
|
|
@ -1,151 +1,15 @@
|
|||
use anyhow::{anyhow, Error, Result};
|
||||
use std::{
|
||||
convert::{TryFrom, TryInto},
|
||||
fmt,
|
||||
ops::Deref,
|
||||
};
|
||||
use himalaya_lib::msg::Flag;
|
||||
|
||||
/// Represents the imap flag variants.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
|
||||
pub enum ImapFlag {
|
||||
Seen,
|
||||
Answered,
|
||||
Flagged,
|
||||
Deleted,
|
||||
Draft,
|
||||
Recent,
|
||||
MayCreate,
|
||||
Custom(String),
|
||||
}
|
||||
|
||||
impl From<&str> for ImapFlag {
|
||||
fn from(flag_str: &str) -> Self {
|
||||
match flag_str {
|
||||
"seen" => ImapFlag::Seen,
|
||||
"answered" | "replied" => ImapFlag::Answered,
|
||||
"flagged" => ImapFlag::Flagged,
|
||||
"deleted" | "trashed" => ImapFlag::Deleted,
|
||||
"draft" => ImapFlag::Draft,
|
||||
"recent" => ImapFlag::Recent,
|
||||
"maycreate" | "may-create" => ImapFlag::MayCreate,
|
||||
flag_str => ImapFlag::Custom(flag_str.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&imap::types::Flag<'_>> for ImapFlag {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(flag: &imap::types::Flag<'_>) -> Result<Self, Self::Error> {
|
||||
Ok(match flag {
|
||||
imap::types::Flag::Seen => ImapFlag::Seen,
|
||||
imap::types::Flag::Answered => ImapFlag::Answered,
|
||||
imap::types::Flag::Flagged => ImapFlag::Flagged,
|
||||
imap::types::Flag::Deleted => ImapFlag::Deleted,
|
||||
imap::types::Flag::Draft => ImapFlag::Draft,
|
||||
imap::types::Flag::Recent => ImapFlag::Recent,
|
||||
imap::types::Flag::MayCreate => ImapFlag::MayCreate,
|
||||
imap::types::Flag::Custom(custom) => ImapFlag::Custom(custom.to_string()),
|
||||
_ => return Err(anyhow!("cannot parse imap flag")),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the imap flags.
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, serde::Serialize)]
|
||||
pub struct ImapFlags(pub Vec<ImapFlag>);
|
||||
|
||||
impl ImapFlags {
|
||||
/// Builds a symbols string
|
||||
pub fn to_symbols_string(&self) -> String {
|
||||
let mut flags = String::new();
|
||||
flags.push_str(if self.contains(&ImapFlag::Seen) {
|
||||
" "
|
||||
} else {
|
||||
"✷"
|
||||
});
|
||||
flags.push_str(if self.contains(&ImapFlag::Answered) {
|
||||
"↵"
|
||||
} else {
|
||||
" "
|
||||
});
|
||||
flags.push_str(if self.contains(&ImapFlag::Flagged) {
|
||||
"⚑"
|
||||
} else {
|
||||
" "
|
||||
});
|
||||
flags
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for ImapFlags {
|
||||
type Target = Vec<ImapFlag>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ImapFlags {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut glue = "";
|
||||
|
||||
for flag in &self.0 {
|
||||
write!(f, "{}", glue)?;
|
||||
match flag {
|
||||
ImapFlag::Seen => write!(f, "\\Seen")?,
|
||||
ImapFlag::Answered => write!(f, "\\Answered")?,
|
||||
ImapFlag::Flagged => write!(f, "\\Flagged")?,
|
||||
ImapFlag::Deleted => write!(f, "\\Deleted")?,
|
||||
ImapFlag::Draft => write!(f, "\\Draft")?,
|
||||
ImapFlag::Recent => write!(f, "\\Recent")?,
|
||||
ImapFlag::MayCreate => write!(f, "\\MayCreate")?,
|
||||
ImapFlag::Custom(custom) => write!(f, "{}", custom)?,
|
||||
}
|
||||
glue = " ";
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Into<Vec<imap::types::Flag<'a>>> for ImapFlags {
|
||||
fn into(self) -> Vec<imap::types::Flag<'a>> {
|
||||
self.0
|
||||
.into_iter()
|
||||
.map(|flag| match flag {
|
||||
ImapFlag::Seen => imap::types::Flag::Seen,
|
||||
ImapFlag::Answered => imap::types::Flag::Answered,
|
||||
ImapFlag::Flagged => imap::types::Flag::Flagged,
|
||||
ImapFlag::Deleted => imap::types::Flag::Deleted,
|
||||
ImapFlag::Draft => imap::types::Flag::Draft,
|
||||
ImapFlag::Recent => imap::types::Flag::Recent,
|
||||
ImapFlag::MayCreate => imap::types::Flag::MayCreate,
|
||||
ImapFlag::Custom(custom) => imap::types::Flag::Custom(custom.into()),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for ImapFlags {
|
||||
fn from(flags_str: &str) -> Self {
|
||||
ImapFlags(
|
||||
flags_str
|
||||
.split_whitespace()
|
||||
.map(|flag_str| flag_str.trim().into())
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[imap::types::Flag<'_>]> for ImapFlags {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(flags: &[imap::types::Flag<'_>]) -> Result<Self, Self::Error> {
|
||||
let mut f = vec![];
|
||||
for flag in flags {
|
||||
f.push(flag.try_into()?);
|
||||
}
|
||||
Ok(Self(f))
|
||||
pub fn from_imap_flag(imap_flag: &imap::types::Flag<'_>) -> Flag {
|
||||
match imap_flag {
|
||||
imap::types::Flag::Seen => Flag::Seen,
|
||||
imap::types::Flag::Answered => Flag::Answered,
|
||||
imap::types::Flag::Flagged => Flag::Flagged,
|
||||
imap::types::Flag::Deleted => Flag::Deleted,
|
||||
imap::types::Flag::Draft => Flag::Draft,
|
||||
imap::types::Flag::Recent => Flag::Recent,
|
||||
imap::types::Flag::MayCreate => Flag::Custom(String::from("MayCreate")),
|
||||
imap::types::Flag::Custom(flag) => Flag::Custom(flag.to_string()),
|
||||
flag => Flag::Custom(flag.to_string()),
|
||||
}
|
||||
}
|
||||
|
|
22
cli/src/backends/imap/imap_flags.rs
Normal file
22
cli/src/backends/imap/imap_flags.rs
Normal file
|
@ -0,0 +1,22 @@
|
|||
use himalaya_lib::msg::{Flag, Flags};
|
||||
|
||||
use super::from_imap_flag;
|
||||
|
||||
pub fn into_imap_flags<'a>(flags: &'a Flags) -> Vec<imap::types::Flag<'a>> {
|
||||
flags
|
||||
.iter()
|
||||
.map(|flag| match flag {
|
||||
Flag::Seen => imap::types::Flag::Seen,
|
||||
Flag::Answered => imap::types::Flag::Answered,
|
||||
Flag::Flagged => imap::types::Flag::Flagged,
|
||||
Flag::Deleted => imap::types::Flag::Deleted,
|
||||
Flag::Draft => imap::types::Flag::Draft,
|
||||
Flag::Recent => imap::types::Flag::Recent,
|
||||
Flag::Custom(flag) => imap::types::Flag::Custom(flag.into()),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn from_imap_flags(imap_flags: &[imap::types::Flag<'_>]) -> Flags {
|
||||
imap_flags.iter().map(from_imap_flag).collect()
|
||||
}
|
|
@ -7,13 +7,14 @@ use anyhow::{anyhow, Context, Result};
|
|||
use himalaya_lib::{
|
||||
account::{AccountConfig, MaildirBackendConfig},
|
||||
mbox::{Mbox, Mboxes},
|
||||
msg::Envelopes,
|
||||
};
|
||||
use log::{debug, info, trace};
|
||||
use std::{convert::TryInto, env, ffi::OsStr, fs, path::PathBuf};
|
||||
use std::{env, ffi::OsStr, fs, path::PathBuf};
|
||||
|
||||
use crate::{
|
||||
backends::{Backend, IdMapper, MaildirEnvelopes, MaildirFlags},
|
||||
msg::{Envelopes, Msg},
|
||||
backends::{maildir_envelopes, Backend, IdMapper},
|
||||
msg::Msg,
|
||||
};
|
||||
|
||||
/// Represents the maildir backend.
|
||||
|
@ -136,12 +137,7 @@ impl<'a> Backend<'a> for MaildirBackend<'a> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn get_envelopes(
|
||||
&mut self,
|
||||
dir: &str,
|
||||
page_size: usize,
|
||||
page: usize,
|
||||
) -> Result<Box<dyn Envelopes>> {
|
||||
fn get_envelopes(&mut self, dir: &str, page_size: usize, page: usize) -> Result<Envelopes> {
|
||||
info!(">> get maildir envelopes");
|
||||
debug!("dir: {:?}", dir);
|
||||
debug!("page size: {:?}", page_size);
|
||||
|
@ -153,7 +149,8 @@ impl<'a> Backend<'a> for MaildirBackend<'a> {
|
|||
|
||||
// Reads envelopes from the "cur" folder of the selected
|
||||
// maildir.
|
||||
let mut envelopes: MaildirEnvelopes = mdir.list_cur().try_into().with_context(|| {
|
||||
let mut envelopes =
|
||||
maildir_envelopes::from_maildir_entries(mdir.list_cur()).with_context(|| {
|
||||
format!("cannot parse maildir envelopes from {:?}", self.mdir.path())
|
||||
})?;
|
||||
debug!("envelopes len: {:?}", envelopes.len());
|
||||
|
@ -185,7 +182,7 @@ impl<'a> Backend<'a> for MaildirBackend<'a> {
|
|||
let mut mapper = IdMapper::new(mdir.path())?;
|
||||
let entries = envelopes
|
||||
.iter()
|
||||
.map(|env| (env.hash.to_owned(), env.id.to_owned()))
|
||||
.map(|env| (env.id.to_owned(), env.internal_id.to_owned()))
|
||||
.collect();
|
||||
mapper.append(entries)?
|
||||
};
|
||||
|
@ -194,10 +191,10 @@ impl<'a> Backend<'a> for MaildirBackend<'a> {
|
|||
// Shorten envelopes hash.
|
||||
envelopes
|
||||
.iter_mut()
|
||||
.for_each(|env| env.hash = env.hash[0..short_hash_len].to_owned());
|
||||
.for_each(|env| env.id = env.id[0..short_hash_len].to_owned());
|
||||
|
||||
info!("<< get maildir envelopes");
|
||||
Ok(Box::new(envelopes))
|
||||
Ok(envelopes)
|
||||
}
|
||||
|
||||
fn search_envelopes(
|
||||
|
@ -207,7 +204,7 @@ impl<'a> Backend<'a> for MaildirBackend<'a> {
|
|||
_sort: &str,
|
||||
_page_size: usize,
|
||||
_page: usize,
|
||||
) -> Result<Box<dyn Envelopes>> {
|
||||
) -> Result<Envelopes> {
|
||||
info!(">> search maildir envelopes");
|
||||
info!("<< search maildir envelopes");
|
||||
Err(anyhow!(
|
||||
|
@ -223,9 +220,6 @@ impl<'a> Backend<'a> for MaildirBackend<'a> {
|
|||
let mdir = self
|
||||
.get_mdir_from_dir(dir)
|
||||
.with_context(|| format!("cannot get maildir instance from {:?}", dir))?;
|
||||
let flags: MaildirFlags = flags
|
||||
.try_into()
|
||||
.with_context(|| format!("cannot parse maildir flags {:?}", flags))?;
|
||||
let id = mdir
|
||||
.store_cur_with_flags(msg, &flags.to_string())
|
||||
.with_context(|| format!("cannot add maildir message to {:?}", mdir.path()))?;
|
||||
|
@ -426,9 +420,6 @@ impl<'a> Backend<'a> for MaildirBackend<'a> {
|
|||
let mdir = self
|
||||
.get_mdir_from_dir(dir)
|
||||
.with_context(|| format!("cannot get maildir instance from {:?}", dir))?;
|
||||
let flags: MaildirFlags = flags
|
||||
.try_into()
|
||||
.with_context(|| format!("cannot parse maildir flags {:?}", flags))?;
|
||||
debug!("flags: {:?}", flags);
|
||||
let id = IdMapper::new(mdir.path())
|
||||
.with_context(|| format!("cannot create id mapper instance for {:?}", mdir.path()))?
|
||||
|
@ -457,9 +448,6 @@ impl<'a> Backend<'a> for MaildirBackend<'a> {
|
|||
let mdir = self
|
||||
.get_mdir_from_dir(dir)
|
||||
.with_context(|| format!("cannot get maildir instance from {:?}", dir))?;
|
||||
let flags: MaildirFlags = flags
|
||||
.try_into()
|
||||
.with_context(|| format!("cannot parse maildir flags {:?}", flags))?;
|
||||
debug!("flags: {:?}", flags);
|
||||
let id = IdMapper::new(mdir.path())
|
||||
.with_context(|| format!("cannot create id mapper instance for {:?}", mdir.path()))?
|
||||
|
@ -488,9 +476,6 @@ impl<'a> Backend<'a> for MaildirBackend<'a> {
|
|||
let mdir = self
|
||||
.get_mdir_from_dir(dir)
|
||||
.with_context(|| format!("cannot get maildir instance from {:?}", dir))?;
|
||||
let flags: MaildirFlags = flags
|
||||
.try_into()
|
||||
.with_context(|| format!("cannot parse maildir flags {:?}", flags))?;
|
||||
debug!("flags: {:?}", flags);
|
||||
let id = IdMapper::new(mdir.path())
|
||||
.with_context(|| format!("cannot create id mapper instance for {:?}", mdir.path()))?
|
||||
|
|
|
@ -1,146 +1,26 @@
|
|||
//! Maildir mailbox module.
|
||||
//!
|
||||
//! This module provides Maildir types and conversion utilities
|
||||
//! related to the envelope
|
||||
|
||||
use anyhow::{anyhow, Context, Error, Result};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use chrono::DateTime;
|
||||
use himalaya_lib::msg::Envelope;
|
||||
use log::trace;
|
||||
use std::{
|
||||
convert::{TryFrom, TryInto},
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
backends::{MaildirFlag, MaildirFlags},
|
||||
backends::maildir_flags,
|
||||
msg::{from_slice_to_addrs, Addr},
|
||||
output::{PrintTable, PrintTableOpts, WriteColor},
|
||||
ui::{Cell, Row, Table},
|
||||
};
|
||||
|
||||
/// Represents a list of envelopes.
|
||||
#[derive(Debug, Default, serde::Serialize)]
|
||||
pub struct MaildirEnvelopes {
|
||||
#[serde(rename = "response")]
|
||||
pub envelopes: Vec<MaildirEnvelope>,
|
||||
}
|
||||
|
||||
impl Deref for MaildirEnvelopes {
|
||||
type Target = Vec<MaildirEnvelope>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.envelopes
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for MaildirEnvelopes {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.envelopes
|
||||
}
|
||||
}
|
||||
|
||||
impl PrintTable for MaildirEnvelopes {
|
||||
fn print_table(&self, writer: &mut dyn WriteColor, opts: PrintTableOpts) -> Result<()> {
|
||||
writeln!(writer)?;
|
||||
Table::print(writer, self, opts)?;
|
||||
writeln!(writer)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// impl Envelopes for MaildirEnvelopes {
|
||||
// //
|
||||
// }
|
||||
|
||||
/// Represents the envelope. The envelope is just a message subset,
|
||||
/// and is mostly used for listings.
|
||||
#[derive(Debug, Default, Clone, serde::Serialize)]
|
||||
pub struct MaildirEnvelope {
|
||||
/// Represents the id of the message.
|
||||
pub id: String,
|
||||
|
||||
/// Represents the MD5 hash of the message id.
|
||||
pub hash: String,
|
||||
|
||||
/// Represents the flags of the message.
|
||||
pub flags: MaildirFlags,
|
||||
|
||||
/// Represents the subject of the message.
|
||||
pub subject: String,
|
||||
|
||||
/// Represents the first sender of the message.
|
||||
pub sender: String,
|
||||
|
||||
/// Represents the date of the message.
|
||||
pub date: String,
|
||||
}
|
||||
|
||||
impl Table for MaildirEnvelope {
|
||||
fn head() -> Row {
|
||||
Row::new()
|
||||
.cell(Cell::new("HASH").bold().underline().white())
|
||||
.cell(Cell::new("FLAGS").bold().underline().white())
|
||||
.cell(Cell::new("SUBJECT").shrinkable().bold().underline().white())
|
||||
.cell(Cell::new("SENDER").bold().underline().white())
|
||||
.cell(Cell::new("DATE").bold().underline().white())
|
||||
}
|
||||
|
||||
fn row(&self) -> Row {
|
||||
let hash = self.hash.clone();
|
||||
let unseen = !self.flags.contains(&MaildirFlag::Seen);
|
||||
let flags = self.flags.to_symbols_string();
|
||||
let subject = &self.subject;
|
||||
let sender = &self.sender;
|
||||
let date = &self.date;
|
||||
Row::new()
|
||||
.cell(Cell::new(hash).bold_if(unseen).red())
|
||||
.cell(Cell::new(flags).bold_if(unseen).white())
|
||||
.cell(Cell::new(subject).shrinkable().bold_if(unseen).green())
|
||||
.cell(Cell::new(sender).bold_if(unseen).blue())
|
||||
.cell(Cell::new(date).bold_if(unseen).yellow())
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a list of raw envelopees returned by the `maildir` crate.
|
||||
pub type RawMaildirEnvelopes = maildir::MailEntries;
|
||||
|
||||
impl<'a> TryFrom<RawMaildirEnvelopes> for MaildirEnvelopes {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(mail_entries: RawMaildirEnvelopes) -> Result<Self, Self::Error> {
|
||||
let mut envelopes = vec![];
|
||||
for entry in mail_entries {
|
||||
let envelope: MaildirEnvelope = entry
|
||||
.context("cannot decode maildir mail entry")?
|
||||
.try_into()
|
||||
.context("cannot parse maildir mail entry")?;
|
||||
envelopes.push(envelope);
|
||||
}
|
||||
|
||||
Ok(MaildirEnvelopes { envelopes })
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the raw envelope returned by the `maildir` crate.
|
||||
pub type RawMaildirEnvelope = maildir::MailEntry;
|
||||
pub type MaildirEnvelope = maildir::MailEntry;
|
||||
|
||||
impl<'a> TryFrom<RawMaildirEnvelope> for MaildirEnvelope {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(mut mail_entry: RawMaildirEnvelope) -> Result<Self, Self::Error> {
|
||||
pub fn from_maildir_entry(mut entry: MaildirEnvelope) -> Result<Envelope> {
|
||||
trace!(">> build envelope from maildir parsed mail");
|
||||
|
||||
let mut envelope = Self::default();
|
||||
let mut envelope = Envelope::default();
|
||||
|
||||
envelope.id = mail_entry.id().into();
|
||||
envelope.hash = format!("{:x}", md5::compute(&envelope.id));
|
||||
envelope.flags = (&mail_entry)
|
||||
.try_into()
|
||||
.context("cannot parse maildir flags")?;
|
||||
envelope.internal_id = entry.id().to_owned();
|
||||
envelope.id = format!("{:x}", md5::compute(&envelope.internal_id));
|
||||
envelope.flags = maildir_flags::from_maildir_entry(&entry);
|
||||
|
||||
let parsed_mail = mail_entry
|
||||
.parsed()
|
||||
.context("cannot parse maildir mail entry")?;
|
||||
let parsed_mail = entry.parsed().context("cannot parse maildir mail entry")?;
|
||||
|
||||
trace!(">> parse headers");
|
||||
for h in parsed_mail.get_headers() {
|
||||
|
@ -155,9 +35,8 @@ impl<'a> TryFrom<RawMaildirEnvelope> for MaildirEnvelope {
|
|||
"date" => {
|
||||
envelope.date =
|
||||
DateTime::parse_from_rfc2822(v.split_at(v.find(" (").unwrap_or(v.len())).0)
|
||||
.context(format!("cannot parse maildir message date {:?}", v))?
|
||||
.naive_local()
|
||||
.to_string();
|
||||
.map(|date| date.naive_local().to_string())
|
||||
.ok()
|
||||
}
|
||||
"subject" => {
|
||||
envelope.subject = v.into();
|
||||
|
@ -190,5 +69,4 @@ impl<'a> TryFrom<RawMaildirEnvelope> for MaildirEnvelope {
|
|||
trace!("envelope: {:?}", envelope);
|
||||
trace!("<< build envelope from maildir parsed mail");
|
||||
Ok(envelope)
|
||||
}
|
||||
}
|
||||
|
|
25
cli/src/backends/maildir/maildir_envelopes.rs
Normal file
25
cli/src/backends/maildir/maildir_envelopes.rs
Normal file
|
@ -0,0 +1,25 @@
|
|||
//! Maildir mailbox module.
|
||||
//!
|
||||
//! This module provides Maildir types and conversion utilities
|
||||
//! related to the envelope
|
||||
|
||||
use himalaya_lib::msg::Envelopes;
|
||||
use anyhow::{Result, Context};
|
||||
|
||||
use super::maildir_envelope;
|
||||
|
||||
/// Represents a list of raw envelopees returned by the `maildir` crate.
|
||||
pub type MaildirEnvelopes = maildir::MailEntries;
|
||||
|
||||
pub fn from_maildir_entries(mail_entries: MaildirEnvelopes) -> Result<Envelopes> {
|
||||
let mut envelopes = Envelopes::default();
|
||||
for entry in mail_entries {
|
||||
envelopes.push(
|
||||
maildir_envelope::from_maildir_entry(
|
||||
entry.context("cannot decode maildir mail entry")?,
|
||||
)
|
||||
.context("cannot parse maildir mail entry")?,
|
||||
);
|
||||
}
|
||||
Ok(envelopes)
|
||||
}
|
|
@ -1,129 +1,13 @@
|
|||
use anyhow::{anyhow, Error, Result};
|
||||
use std::{
|
||||
convert::{TryFrom, TryInto},
|
||||
ops::Deref,
|
||||
};
|
||||
use himalaya_lib::msg::Flag;
|
||||
|
||||
/// Represents the maildir flag variants.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
|
||||
pub enum MaildirFlag {
|
||||
Passed,
|
||||
Replied,
|
||||
Seen,
|
||||
Trashed,
|
||||
Draft,
|
||||
Flagged,
|
||||
Custom(char),
|
||||
}
|
||||
|
||||
/// Represents the maildir flags.
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, serde::Serialize)]
|
||||
pub struct MaildirFlags(pub Vec<MaildirFlag>);
|
||||
|
||||
impl MaildirFlags {
|
||||
/// Builds a symbols string
|
||||
pub fn to_symbols_string(&self) -> String {
|
||||
let mut flags = String::new();
|
||||
flags.push_str(if self.contains(&MaildirFlag::Seen) {
|
||||
" "
|
||||
} else {
|
||||
"✷"
|
||||
});
|
||||
flags.push_str(if self.contains(&MaildirFlag::Replied) {
|
||||
"↵"
|
||||
} else {
|
||||
" "
|
||||
});
|
||||
flags.push_str(if self.contains(&MaildirFlag::Passed) {
|
||||
"↗"
|
||||
} else {
|
||||
" "
|
||||
});
|
||||
flags.push_str(if self.contains(&MaildirFlag::Flagged) {
|
||||
"⚑"
|
||||
} else {
|
||||
" "
|
||||
});
|
||||
flags
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for MaildirFlags {
|
||||
type Target = Vec<MaildirFlag>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for MaildirFlags {
|
||||
fn to_string(&self) -> String {
|
||||
self.0
|
||||
.iter()
|
||||
.map(|flag| {
|
||||
let flag_char: char = flag.into();
|
||||
flag_char
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for MaildirFlags {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(flags_str: &str) -> Result<Self, Self::Error> {
|
||||
let mut flags = vec![];
|
||||
for flag_str in flags_str.split_whitespace() {
|
||||
flags.push(flag_str.trim().try_into()?);
|
||||
}
|
||||
Ok(MaildirFlags(flags))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&maildir::MailEntry> for MaildirFlags {
|
||||
fn from(mail_entry: &maildir::MailEntry) -> Self {
|
||||
let mut flags = vec![];
|
||||
for c in mail_entry.flags().chars() {
|
||||
flags.push(match c {
|
||||
'P' => MaildirFlag::Passed,
|
||||
'R' => MaildirFlag::Replied,
|
||||
'S' => MaildirFlag::Seen,
|
||||
'T' => MaildirFlag::Trashed,
|
||||
'D' => MaildirFlag::Draft,
|
||||
'F' => MaildirFlag::Flagged,
|
||||
custom => MaildirFlag::Custom(custom),
|
||||
})
|
||||
}
|
||||
Self(flags)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<char> for &MaildirFlag {
|
||||
fn into(self) -> char {
|
||||
match self {
|
||||
MaildirFlag::Passed => 'P',
|
||||
MaildirFlag::Replied => 'R',
|
||||
MaildirFlag::Seen => 'S',
|
||||
MaildirFlag::Trashed => 'T',
|
||||
MaildirFlag::Draft => 'D',
|
||||
MaildirFlag::Flagged => 'F',
|
||||
MaildirFlag::Custom(custom) => *custom,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for MaildirFlag {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(flag_str: &str) -> Result<Self, Self::Error> {
|
||||
match flag_str {
|
||||
"passed" => Ok(MaildirFlag::Passed),
|
||||
"replied" => Ok(MaildirFlag::Replied),
|
||||
"seen" => Ok(MaildirFlag::Seen),
|
||||
"trashed" => Ok(MaildirFlag::Trashed),
|
||||
"draft" => Ok(MaildirFlag::Draft),
|
||||
"flagged" => Ok(MaildirFlag::Flagged),
|
||||
flag_str => Err(anyhow!("cannot parse maildir flag {:?}", flag_str)),
|
||||
}
|
||||
pub fn from_char(c: char) -> Flag {
|
||||
match c {
|
||||
'R' => Flag::Answered,
|
||||
'S' => Flag::Seen,
|
||||
'T' => Flag::Deleted,
|
||||
'D' => Flag::Draft,
|
||||
'F' => Flag::Flagged,
|
||||
'P' => Flag::Custom(String::from("Passed")),
|
||||
flag => Flag::Custom(flag.to_string()),
|
||||
}
|
||||
}
|
||||
|
|
7
cli/src/backends/maildir/maildir_flags.rs
Normal file
7
cli/src/backends/maildir/maildir_flags.rs
Normal file
|
@ -0,0 +1,7 @@
|
|||
use himalaya_lib::msg::Flags;
|
||||
|
||||
use super::maildir_flag;
|
||||
|
||||
pub fn from_maildir_entry(entry: &maildir::MailEntry) -> Flags {
|
||||
entry.flags().chars().map(maildir_flag::from_char).collect()
|
||||
}
|
|
@ -1,15 +1,16 @@
|
|||
use std::{convert::TryInto, fs};
|
||||
use std::fs;
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use himalaya_lib::{
|
||||
account::{AccountConfig, NotmuchBackendConfig},
|
||||
mbox::{Mbox, Mboxes},
|
||||
msg::Envelopes,
|
||||
};
|
||||
use log::{debug, info, trace};
|
||||
|
||||
use crate::{
|
||||
backends::{Backend, IdMapper, MaildirBackend, NotmuchEnvelopes},
|
||||
msg::{Envelopes, Msg},
|
||||
backends::{notmuch_envelopes, Backend, IdMapper, MaildirBackend},
|
||||
msg::Msg,
|
||||
};
|
||||
|
||||
/// Represents the Notmuch backend.
|
||||
|
@ -53,16 +54,16 @@ impl<'a> NotmuchBackend<'a> {
|
|||
query: &str,
|
||||
page_size: usize,
|
||||
page: usize,
|
||||
) -> Result<Box<dyn Envelopes>> {
|
||||
) -> Result<Envelopes> {
|
||||
// Gets envelopes matching the given Notmuch query.
|
||||
let query_builder = self
|
||||
.db
|
||||
.create_query(query)
|
||||
.with_context(|| format!("cannot create notmuch query from {:?}", query))?;
|
||||
let mut envelopes: NotmuchEnvelopes = query_builder
|
||||
.search_messages()
|
||||
.with_context(|| format!("cannot find notmuch envelopes from query {:?}", query))?
|
||||
.try_into()
|
||||
let mut envelopes =
|
||||
notmuch_envelopes::from_notmuch_msgs(query_builder.search_messages().with_context(
|
||||
|| format!("cannot find notmuch envelopes from query {:?}", query),
|
||||
)?)
|
||||
.with_context(|| format!("cannot parse notmuch envelopes from query {:?}", query))?;
|
||||
debug!("envelopes len: {:?}", envelopes.len());
|
||||
trace!("envelopes: {:?}", envelopes);
|
||||
|
@ -93,7 +94,7 @@ impl<'a> NotmuchBackend<'a> {
|
|||
let mut mapper = IdMapper::new(&self.notmuch_config.notmuch_database_dir)?;
|
||||
let entries = envelopes
|
||||
.iter()
|
||||
.map(|env| (env.hash.to_owned(), env.id.to_owned()))
|
||||
.map(|env| (env.id.to_owned(), env.internal_id.to_owned()))
|
||||
.collect();
|
||||
mapper.append(entries)?
|
||||
};
|
||||
|
@ -102,9 +103,9 @@ impl<'a> NotmuchBackend<'a> {
|
|||
// Shorten envelopes hash.
|
||||
envelopes
|
||||
.iter_mut()
|
||||
.for_each(|env| env.hash = env.hash[0..short_hash_len].to_owned());
|
||||
.for_each(|env| env.id = env.id[0..short_hash_len].to_owned());
|
||||
|
||||
Ok(Box::new(envelopes))
|
||||
Ok(envelopes)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -148,7 +149,7 @@ impl<'a> Backend<'a> for NotmuchBackend<'a> {
|
|||
virt_mbox: &str,
|
||||
page_size: usize,
|
||||
page: usize,
|
||||
) -> Result<Box<dyn Envelopes>> {
|
||||
) -> Result<Envelopes> {
|
||||
info!(">> get notmuch envelopes");
|
||||
debug!("virtual mailbox: {:?}", virt_mbox);
|
||||
debug!("page size: {:?}", page_size);
|
||||
|
@ -174,7 +175,7 @@ impl<'a> Backend<'a> for NotmuchBackend<'a> {
|
|||
_sort: &str,
|
||||
page_size: usize,
|
||||
page: usize,
|
||||
) -> Result<Box<dyn Envelopes>> {
|
||||
) -> Result<Envelopes> {
|
||||
info!(">> search notmuch envelopes");
|
||||
debug!("virtual mailbox: {:?}", virt_mbox);
|
||||
debug!("query: {:?}", query);
|
||||
|
|
|
@ -3,128 +3,21 @@
|
|||
//! This module provides Notmuch types and conversion utilities
|
||||
//! related to the envelope
|
||||
|
||||
use anyhow::{anyhow, Context, Error, Result};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use chrono::DateTime;
|
||||
use himalaya_lib::msg::{Envelope, Flag};
|
||||
use log::{info, trace};
|
||||
use std::{
|
||||
convert::{TryFrom, TryInto},
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
msg::{from_slice_to_addrs, Addr},
|
||||
output::{PrintTable, PrintTableOpts, WriteColor},
|
||||
ui::{Cell, Row, Table},
|
||||
};
|
||||
|
||||
/// Represents a list of envelopes.
|
||||
#[derive(Debug, Default, serde::Serialize)]
|
||||
pub struct NotmuchEnvelopes {
|
||||
#[serde(rename = "response")]
|
||||
pub envelopes: Vec<NotmuchEnvelope>,
|
||||
}
|
||||
|
||||
impl Deref for NotmuchEnvelopes {
|
||||
type Target = Vec<NotmuchEnvelope>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.envelopes
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for NotmuchEnvelopes {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.envelopes
|
||||
}
|
||||
}
|
||||
|
||||
impl PrintTable for NotmuchEnvelopes {
|
||||
fn print_table(&self, writer: &mut dyn WriteColor, opts: PrintTableOpts) -> Result<()> {
|
||||
writeln!(writer)?;
|
||||
Table::print(writer, self, opts)?;
|
||||
writeln!(writer)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the envelope. The envelope is just a message subset,
|
||||
/// and is mostly used for listings.
|
||||
#[derive(Debug, Default, Clone, serde::Serialize)]
|
||||
pub struct NotmuchEnvelope {
|
||||
/// Represents the id of the message.
|
||||
pub id: String,
|
||||
|
||||
/// Represents the MD5 hash of the message id.
|
||||
pub hash: String,
|
||||
|
||||
/// Represents the tags of the message.
|
||||
pub flags: Vec<String>,
|
||||
|
||||
/// Represents the subject of the message.
|
||||
pub subject: String,
|
||||
|
||||
/// Represents the first sender of the message.
|
||||
pub sender: String,
|
||||
|
||||
/// Represents the date of the message.
|
||||
pub date: String,
|
||||
}
|
||||
|
||||
impl Table for NotmuchEnvelope {
|
||||
fn head() -> Row {
|
||||
Row::new()
|
||||
.cell(Cell::new("HASH").bold().underline().white())
|
||||
.cell(Cell::new("FLAGS").bold().underline().white())
|
||||
.cell(Cell::new("SUBJECT").shrinkable().bold().underline().white())
|
||||
.cell(Cell::new("SENDER").bold().underline().white())
|
||||
.cell(Cell::new("DATE").bold().underline().white())
|
||||
}
|
||||
|
||||
fn row(&self) -> Row {
|
||||
let hash = self.hash.to_string();
|
||||
let unseen = !self.flags.contains(&String::from("unread"));
|
||||
let flags = String::new();
|
||||
let subject = &self.subject;
|
||||
let sender = &self.sender;
|
||||
let date = &self.date;
|
||||
Row::new()
|
||||
.cell(Cell::new(hash).bold_if(unseen).red())
|
||||
.cell(Cell::new(flags).bold_if(unseen).white())
|
||||
.cell(Cell::new(subject).shrinkable().bold_if(unseen).green())
|
||||
.cell(Cell::new(sender).bold_if(unseen).blue())
|
||||
.cell(Cell::new(date).bold_if(unseen).yellow())
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a list of raw envelopees returned by the `notmuch` crate.
|
||||
pub type RawNotmuchEnvelopes = notmuch::Messages;
|
||||
|
||||
impl<'a> TryFrom<RawNotmuchEnvelopes> for NotmuchEnvelopes {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(raw_envelopes: RawNotmuchEnvelopes) -> Result<Self, Self::Error> {
|
||||
let mut envelopes = vec![];
|
||||
for raw_envelope in raw_envelopes {
|
||||
let envelope: NotmuchEnvelope = raw_envelope
|
||||
.try_into()
|
||||
.context("cannot parse notmuch mail entry")?;
|
||||
envelopes.push(envelope);
|
||||
}
|
||||
Ok(NotmuchEnvelopes { envelopes })
|
||||
}
|
||||
}
|
||||
use crate::msg::{from_slice_to_addrs, Addr};
|
||||
|
||||
/// Represents the raw envelope returned by the `notmuch` crate.
|
||||
pub type RawNotmuchEnvelope = notmuch::Message;
|
||||
|
||||
impl<'a> TryFrom<RawNotmuchEnvelope> for NotmuchEnvelope {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(raw_envelope: RawNotmuchEnvelope) -> Result<Self, Self::Error> {
|
||||
pub fn from_notmuch_msg(raw_envelope: RawNotmuchEnvelope) -> Result<Envelope> {
|
||||
info!("begin: try building envelope from notmuch parsed mail");
|
||||
|
||||
let id = raw_envelope.id().to_string();
|
||||
let hash = format!("{:x}", md5::compute(&id));
|
||||
let internal_id = raw_envelope.id().to_string();
|
||||
let id = format!("{:x}", md5::compute(&internal_id));
|
||||
let subject = raw_envelope
|
||||
.header("subject")
|
||||
.context("cannot get header \"Subject\" from notmuch message")?
|
||||
|
@ -133,7 +26,7 @@ impl<'a> TryFrom<RawNotmuchEnvelope> for NotmuchEnvelope {
|
|||
let sender = raw_envelope
|
||||
.header("from")
|
||||
.context("cannot get header \"From\" from notmuch message")?
|
||||
.ok_or_else(|| anyhow!("cannot parse sender from notmuch message {:?}", id))?
|
||||
.ok_or_else(|| anyhow!("cannot parse sender from notmuch message {:?}", internal_id))?
|
||||
.to_string();
|
||||
let sender = from_slice_to_addrs(sender)?
|
||||
.and_then(|senders| {
|
||||
|
@ -153,21 +46,23 @@ impl<'a> TryFrom<RawNotmuchEnvelope> for NotmuchEnvelope {
|
|||
let date = raw_envelope
|
||||
.header("date")
|
||||
.context("cannot get header \"Date\" from notmuch message")?
|
||||
.ok_or_else(|| anyhow!("cannot parse date of notmuch message {:?}", id))?
|
||||
.ok_or_else(|| anyhow!("cannot parse date of notmuch message {:?}", internal_id))?
|
||||
.to_string();
|
||||
let date =
|
||||
DateTime::parse_from_rfc2822(date.split_at(date.find(" (").unwrap_or(date.len())).0)
|
||||
let date = DateTime::parse_from_rfc2822(date.split_at(date.find(" (").unwrap_or(date.len())).0)
|
||||
.context(format!(
|
||||
"cannot parse message date {:?} of notmuch message {:?}",
|
||||
date, id
|
||||
))?
|
||||
.naive_local()
|
||||
.to_string();
|
||||
date, internal_id
|
||||
))
|
||||
.map(|date| date.naive_local().to_string())
|
||||
.ok();
|
||||
|
||||
let envelope = Self {
|
||||
let envelope = Envelope {
|
||||
id,
|
||||
hash,
|
||||
flags: raw_envelope.tags().collect(),
|
||||
internal_id,
|
||||
flags: raw_envelope
|
||||
.tags()
|
||||
.map(|tag| Flag::Custom(tag.to_string()))
|
||||
.collect(),
|
||||
subject,
|
||||
sender,
|
||||
date,
|
||||
|
@ -176,5 +71,4 @@ impl<'a> TryFrom<RawNotmuchEnvelope> for NotmuchEnvelope {
|
|||
|
||||
info!("end: try building envelope from notmuch parsed mail");
|
||||
Ok(envelope)
|
||||
}
|
||||
}
|
||||
|
|
18
cli/src/backends/notmuch/notmuch_envelopes.rs
Normal file
18
cli/src/backends/notmuch/notmuch_envelopes.rs
Normal file
|
@ -0,0 +1,18 @@
|
|||
use anyhow::{Context, Result};
|
||||
use himalaya_lib::msg::Envelopes;
|
||||
|
||||
use super::notmuch_envelope;
|
||||
|
||||
/// Represents a list of raw envelopees returned by the `notmuch`
|
||||
/// crate.
|
||||
pub type RawNotmuchEnvelopes = notmuch::Messages;
|
||||
|
||||
pub fn from_notmuch_msgs(msgs: RawNotmuchEnvelopes) -> Result<Envelopes> {
|
||||
let mut envelopes = Envelopes::default();
|
||||
for msg in msgs {
|
||||
let envelope =
|
||||
notmuch_envelope::from_notmuch_msg(msg).context("cannot parse notmuch message")?;
|
||||
envelopes.push(envelope);
|
||||
}
|
||||
Ok(envelopes)
|
||||
}
|
|
@ -13,6 +13,9 @@ pub mod msg {
|
|||
pub mod envelope;
|
||||
pub use envelope::*;
|
||||
|
||||
pub mod envelopes;
|
||||
pub use envelopes::*;
|
||||
|
||||
pub mod msg_args;
|
||||
|
||||
pub mod msg_handlers;
|
||||
|
@ -52,9 +55,15 @@ pub mod backends {
|
|||
|
||||
pub mod imap_handlers;
|
||||
|
||||
pub mod imap_envelopes;
|
||||
pub use imap_envelopes::*;
|
||||
|
||||
pub mod imap_envelope;
|
||||
pub use imap_envelope::*;
|
||||
|
||||
pub mod imap_flags;
|
||||
pub use imap_flags::*;
|
||||
|
||||
pub mod imap_flag;
|
||||
pub use imap_flag::*;
|
||||
|
||||
|
@ -69,9 +78,15 @@ pub mod backends {
|
|||
pub mod maildir_backend;
|
||||
pub use maildir_backend::*;
|
||||
|
||||
pub mod maildir_envelopes;
|
||||
pub use maildir_envelopes::*;
|
||||
|
||||
pub mod maildir_envelope;
|
||||
pub use maildir_envelope::*;
|
||||
|
||||
pub mod maildir_flags;
|
||||
pub use maildir_flags::*;
|
||||
|
||||
pub mod maildir_flag;
|
||||
pub use maildir_flag::*;
|
||||
}
|
||||
|
@ -84,6 +99,9 @@ pub mod backends {
|
|||
pub mod notmuch_backend;
|
||||
pub use notmuch_backend::*;
|
||||
|
||||
pub mod notmuch_envelopes;
|
||||
pub use notmuch_envelopes::*;
|
||||
|
||||
pub mod notmuch_envelope;
|
||||
pub use notmuch_envelope::*;
|
||||
}
|
||||
|
|
|
@ -1,13 +1,30 @@
|
|||
use std::{any, fmt};
|
||||
use himalaya_lib::msg::{Envelope, Flag};
|
||||
|
||||
use crate::output::PrintTable;
|
||||
use crate::ui::{Cell, Row, Table};
|
||||
|
||||
pub trait Envelopes: fmt::Debug + erased_serde::Serialize + PrintTable + any::Any {
|
||||
fn as_any(&self) -> &dyn any::Any;
|
||||
}
|
||||
impl Table for Envelope {
|
||||
fn head() -> Row {
|
||||
Row::new()
|
||||
.cell(Cell::new("ID").bold().underline().white())
|
||||
.cell(Cell::new("FLAGS").bold().underline().white())
|
||||
.cell(Cell::new("SUBJECT").shrinkable().bold().underline().white())
|
||||
.cell(Cell::new("SENDER").bold().underline().white())
|
||||
.cell(Cell::new("DATE").bold().underline().white())
|
||||
}
|
||||
|
||||
impl<T: fmt::Debug + erased_serde::Serialize + PrintTable + any::Any> Envelopes for T {
|
||||
fn as_any(&self) -> &dyn any::Any {
|
||||
self
|
||||
fn row(&self) -> Row {
|
||||
let id = self.id.to_string();
|
||||
let flags = self.flags.to_symbols_string();
|
||||
let unseen = !self.flags.contains(&Flag::Seen);
|
||||
let subject = &self.subject;
|
||||
let sender = &self.sender;
|
||||
let date = self.date.as_deref().unwrap_or_default();
|
||||
|
||||
Row::new()
|
||||
.cell(Cell::new(id).bold_if(unseen).red())
|
||||
.cell(Cell::new(flags).bold_if(unseen).white())
|
||||
.cell(Cell::new(subject).shrinkable().bold_if(unseen).green())
|
||||
.cell(Cell::new(sender).bold_if(unseen).blue())
|
||||
.cell(Cell::new(date).bold_if(unseen).yellow())
|
||||
}
|
||||
}
|
||||
|
|
16
cli/src/msg/envelopes.rs
Normal file
16
cli/src/msg/envelopes.rs
Normal file
|
@ -0,0 +1,16 @@
|
|||
use anyhow::Result;
|
||||
use himalaya_lib::msg::Envelopes;
|
||||
|
||||
use crate::{
|
||||
output::{PrintTable, PrintTableOpts, WriteColor},
|
||||
ui::Table,
|
||||
};
|
||||
|
||||
impl PrintTable for Envelopes {
|
||||
fn print_table(&self, writer: &mut dyn WriteColor, opts: PrintTableOpts) -> Result<()> {
|
||||
writeln!(writer)?;
|
||||
Table::print(writer, self, opts)?;
|
||||
writeln!(writer)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -126,7 +126,7 @@ pub fn list<'a, P: PrinterService, B: Backend<'a> + ?Sized>(
|
|||
let msgs = imap.get_envelopes(mbox, page_size, page)?;
|
||||
trace!("envelopes: {:?}", msgs);
|
||||
printer.print_table(
|
||||
msgs,
|
||||
Box::new(msgs),
|
||||
PrintTableOpts {
|
||||
format: &config.format,
|
||||
max_width,
|
||||
|
@ -310,7 +310,7 @@ pub fn search<'a, P: PrinterService, B: Backend<'a> + ?Sized>(
|
|||
let msgs = backend.search_envelopes(mbox, &query, "", page_size, page)?;
|
||||
trace!("messages: {:#?}", msgs);
|
||||
printer.print_table(
|
||||
msgs,
|
||||
Box::new(msgs),
|
||||
PrintTableOpts {
|
||||
format: &config.format,
|
||||
max_width,
|
||||
|
@ -335,7 +335,7 @@ pub fn sort<'a, P: PrinterService, B: Backend<'a> + ?Sized>(
|
|||
let msgs = backend.search_envelopes(mbox, &query, &sort, page_size, page)?;
|
||||
trace!("envelopes: {:#?}", msgs);
|
||||
printer.print_table(
|
||||
msgs,
|
||||
Box::new(msgs),
|
||||
PrintTableOpts {
|
||||
format: &config.format,
|
||||
max_width,
|
||||
|
|
|
@ -7,7 +7,7 @@ edition = "2021"
|
|||
imap-backend = ["imap", "imap-proto"]
|
||||
maildir-backend = ["maildir", "md5"]
|
||||
notmuch-backend = ["notmuch", "maildir-backend"]
|
||||
default = ["imap-backend", "maildir-backend", "notmuch-backend"]
|
||||
default = ["imap-backend", "maildir-backend"]
|
||||
|
||||
[dependencies]
|
||||
lettre = { version = "0.10.0-rc.6", features = ["serde"] }
|
||||
|
|
|
@ -2,3 +2,4 @@ mod process;
|
|||
|
||||
pub mod account;
|
||||
pub mod mbox;
|
||||
pub mod msg;
|
||||
|
|
21
lib/src/msg/envelope.rs
Normal file
21
lib/src/msg/envelope.rs
Normal file
|
@ -0,0 +1,21 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use super::Flags;
|
||||
|
||||
/// Represents the message envelope. The envelope is just a message
|
||||
/// subset, and is mostly used for listings.
|
||||
#[derive(Debug, Default, Clone, Serialize)]
|
||||
pub struct Envelope {
|
||||
/// Represents the message identifier.
|
||||
pub id: String,
|
||||
/// Represents the internal message identifier.
|
||||
pub internal_id: String,
|
||||
/// Represents the message flags.
|
||||
pub flags: Flags,
|
||||
/// Represents the subject of the message.
|
||||
pub subject: String,
|
||||
/// Represents the first sender of the message.
|
||||
pub sender: String,
|
||||
/// Represents the internal date of the message.
|
||||
pub date: Option<String>,
|
||||
}
|
25
lib/src/msg/envelopes.rs
Normal file
25
lib/src/msg/envelopes.rs
Normal file
|
@ -0,0 +1,25 @@
|
|||
use serde::Serialize;
|
||||
use std::ops;
|
||||
|
||||
use super::Envelope;
|
||||
|
||||
/// Represents the list of envelopes.
|
||||
#[derive(Debug, Default, Serialize)]
|
||||
pub struct Envelopes {
|
||||
#[serde(rename = "response")]
|
||||
pub envelopes: Vec<Envelope>,
|
||||
}
|
||||
|
||||
impl ops::Deref for Envelopes {
|
||||
type Target = Vec<Envelope>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.envelopes
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::DerefMut for Envelopes {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.envelopes
|
||||
}
|
||||
}
|
27
lib/src/msg/flag.rs
Normal file
27
lib/src/msg/flag.rs
Normal file
|
@ -0,0 +1,27 @@
|
|||
use serde::Serialize;
|
||||
|
||||
/// Represents the flag variants.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub enum Flag {
|
||||
Seen,
|
||||
Answered,
|
||||
Flagged,
|
||||
Deleted,
|
||||
Draft,
|
||||
Recent,
|
||||
Custom(String),
|
||||
}
|
||||
|
||||
impl From<&str> for Flag {
|
||||
fn from(flag_str: &str) -> Self {
|
||||
match flag_str {
|
||||
"seen" => Flag::Seen,
|
||||
"answered" | "replied" => Flag::Answered,
|
||||
"flagged" => Flag::Flagged,
|
||||
"deleted" | "trashed" => Flag::Deleted,
|
||||
"draft" => Flag::Draft,
|
||||
"recent" => Flag::Recent,
|
||||
flag => Flag::Custom(flag.into()),
|
||||
}
|
||||
}
|
||||
}
|
89
lib/src/msg/flags.rs
Normal file
89
lib/src/msg/flags.rs
Normal file
|
@ -0,0 +1,89 @@
|
|||
use std::{fmt, ops};
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
use super::Flag;
|
||||
|
||||
/// Represents the list of flags.
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct Flags(pub Vec<Flag>);
|
||||
|
||||
impl Flags {
|
||||
/// Builds a symbols string.
|
||||
pub fn to_symbols_string(&self) -> String {
|
||||
let mut flags = String::new();
|
||||
flags.push_str(if self.contains(&Flag::Seen) {
|
||||
" "
|
||||
} else {
|
||||
"✷"
|
||||
});
|
||||
flags.push_str(if self.contains(&Flag::Answered) {
|
||||
"↵"
|
||||
} else {
|
||||
" "
|
||||
});
|
||||
flags.push_str(if self.contains(&Flag::Flagged) {
|
||||
"⚑"
|
||||
} else {
|
||||
" "
|
||||
});
|
||||
flags
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Deref for Flags {
|
||||
type Target = Vec<Flag>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::DerefMut for Flags {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Flags {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut glue = "";
|
||||
|
||||
for flag in &self.0 {
|
||||
write!(f, "{}", glue)?;
|
||||
match flag {
|
||||
Flag::Seen => write!(f, "\\Seen")?,
|
||||
Flag::Answered => write!(f, "\\Answered")?,
|
||||
Flag::Flagged => write!(f, "\\Flagged")?,
|
||||
Flag::Deleted => write!(f, "\\Deleted")?,
|
||||
Flag::Draft => write!(f, "\\Draft")?,
|
||||
Flag::Recent => write!(f, "\\Recent")?,
|
||||
Flag::Custom(flag) => write!(f, "{}", flag)?,
|
||||
}
|
||||
glue = " ";
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for Flags {
|
||||
fn from(flags: &str) -> Self {
|
||||
Flags(
|
||||
flags
|
||||
.split_whitespace()
|
||||
.map(|flag| flag.trim().into())
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<Flag> for Flags {
|
||||
fn from_iter<T: IntoIterator<Item = Flag>>(iter: T) -> Self {
|
||||
let mut flags = Flags::default();
|
||||
for flag in iter {
|
||||
flags.push(flag);
|
||||
}
|
||||
flags
|
||||
}
|
||||
}
|
11
lib/src/msg/mod.rs
Normal file
11
lib/src/msg/mod.rs
Normal file
|
@ -0,0 +1,11 @@
|
|||
mod flag;
|
||||
pub use flag::*;
|
||||
|
||||
mod flags;
|
||||
pub use flags::*;
|
||||
|
||||
mod envelope;
|
||||
pub use envelope::*;
|
||||
|
||||
mod envelopes;
|
||||
pub use envelopes::*;
|
Loading…
Reference in a new issue