melib/mbox: add MboxMetadata type and write support
parent
3fa9e355c2
commit
cf9457882a
|
@ -101,6 +101,8 @@
|
|||
//! mbox_1,
|
||||
//! None, // Envelope From
|
||||
//! Some(melib::datetime::now()), // Delivered date
|
||||
//! Default::default(), // Flags and tags
|
||||
//! MboxMetadata::None,
|
||||
//! true,
|
||||
//! false,
|
||||
//! )?;
|
||||
|
@ -109,6 +111,8 @@
|
|||
//! mbox_2,
|
||||
//! None,
|
||||
//! Some(melib::datetime::now()),
|
||||
//! Default::default(), // Flags and tags
|
||||
//! MboxMetadata::None,
|
||||
//! false,
|
||||
//! false,
|
||||
//! )?;
|
||||
|
@ -366,6 +370,20 @@ impl BackendOp for MboxOp {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum MboxMetadata {
|
||||
/// Dovecot uses C-Client (ie. UW-IMAP, Pine) compatible headers in mbox messages to store me
|
||||
/// - X-IMAPbase: Contains UIDVALIDITY, last used UID and list of used keywords
|
||||
/// - X-IMAP: Same as X-IMAPbase but also specifies that the message is a “pseudo message”
|
||||
/// - X-UID: Message’s allocated UID
|
||||
/// - Status: R (Seen) and O (non-Recent) flags
|
||||
/// - X-Status: A (Answered), F (Flagged), T (Draft) and D (Deleted) flags
|
||||
/// - X-Keywords: Message’s keywords
|
||||
/// - Content-Length: Length of the message body in bytes
|
||||
CClient,
|
||||
None,
|
||||
}
|
||||
|
||||
/// Choose between "mboxo", "mboxrd", "mboxcl", "mboxcl2". For new mailboxes, prefer "mboxcl2"
|
||||
/// which does not alter the mail body.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
|
|
|
@ -28,9 +28,14 @@ impl MboxFormat {
|
|||
input: &[u8],
|
||||
envelope_from: Option<&Address>,
|
||||
delivery_date: Option<crate::UnixTimestamp>,
|
||||
(flags, tags): (Flag, Vec<&str>),
|
||||
metadata_format: MboxMetadata,
|
||||
is_empty: bool,
|
||||
crlf: bool,
|
||||
) -> Result<()> {
|
||||
if tags.iter().any(|t| t.contains(' ')) {
|
||||
return Err(MeliError::new("mbox tags/keywords can't contain spaces"));
|
||||
}
|
||||
let line_ending: &'static [u8] = if crlf { &b"\r\n"[..] } else { &b"\n"[..] };
|
||||
if !is_empty {
|
||||
writer.write_all(line_ending)?;
|
||||
|
@ -54,12 +59,60 @@ impl MboxFormat {
|
|||
)?;
|
||||
writer.write_all(line_ending)?;
|
||||
let (mut headers, body) = parser::mail(input)?;
|
||||
headers.retain(|(header_name, _)| {
|
||||
!header_name.eq_ignore_ascii_case(b"Status")
|
||||
&& !header_name.eq_ignore_ascii_case(b"X-Status")
|
||||
&& !header_name.eq_ignore_ascii_case(b"X-Keywords")
|
||||
&& !header_name.eq_ignore_ascii_case(b"Content-Length")
|
||||
});
|
||||
let write_metadata_fn = |writer: &mut dyn std::io::Write| match metadata_format {
|
||||
MboxMetadata::CClient => {
|
||||
for (h, v) in {
|
||||
if flags.is_seen() {
|
||||
Some((&b"Status"[..], "R".into()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
.into_iter()
|
||||
.chain(
|
||||
if !flags.is_flagged()
|
||||
&& !flags.is_replied()
|
||||
&& !flags.is_draft()
|
||||
&& !flags.is_trashed()
|
||||
{
|
||||
None
|
||||
} else {
|
||||
Some((
|
||||
&b"X-Status"[..],
|
||||
format!(
|
||||
"{flagged}{replied}{draft}{trashed}",
|
||||
flagged = if flags.is_flagged() { "F" } else { "" },
|
||||
replied = if flags.is_replied() { "A" } else { "" },
|
||||
draft = if flags.is_draft() { "T" } else { "" },
|
||||
trashed = if flags.is_trashed() { "D" } else { "" }
|
||||
),
|
||||
))
|
||||
},
|
||||
)
|
||||
.chain(if tags.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some((&b"X-Keywords"[..], tags.as_slice().join(" ")))
|
||||
})
|
||||
} {
|
||||
writer.write_all(h)?;
|
||||
writer.write_all(&b": "[..])?;
|
||||
writer.write_all(v.as_bytes())?;
|
||||
writer.write_all(line_ending)?;
|
||||
}
|
||||
Ok::<(), MeliError>(())
|
||||
}
|
||||
MboxMetadata::None => Ok(()),
|
||||
};
|
||||
|
||||
match self {
|
||||
MboxFormat::MboxO | MboxFormat::MboxRd => Err(MeliError::new("Unimplemented.")),
|
||||
MboxFormat::MboxCl => {
|
||||
headers.retain(|(header_name, _)| {
|
||||
!header_name.eq_ignore_ascii_case(b"Content-Length")
|
||||
});
|
||||
let len = (body.len()
|
||||
+ body
|
||||
.windows(b"\nFrom ".len())
|
||||
|
@ -76,6 +129,7 @@ impl MboxFormat {
|
|||
writer.write_all(v)?;
|
||||
writer.write_all(line_ending)?;
|
||||
}
|
||||
write_metadata_fn(writer)?;
|
||||
writer.write_all(line_ending)?;
|
||||
|
||||
if body.starts_with(b"From ") {
|
||||
|
@ -90,9 +144,6 @@ impl MboxFormat {
|
|||
Ok(())
|
||||
}
|
||||
MboxFormat::MboxCl2 => {
|
||||
headers.retain(|(header_name, _)| {
|
||||
!header_name.eq_ignore_ascii_case(b"Content-Length")
|
||||
});
|
||||
let len = body.len().to_string();
|
||||
for (h, v) in headers
|
||||
.into_iter()
|
||||
|
@ -103,6 +154,7 @@ impl MboxFormat {
|
|||
writer.write_all(v)?;
|
||||
writer.write_all(line_ending)?;
|
||||
}
|
||||
write_metadata_fn(writer)?;
|
||||
writer.write_all(line_ending)?;
|
||||
writer.write_all(body)?;
|
||||
Ok(())
|
||||
|
|
|
@ -359,6 +359,7 @@ pub trait MailListingTrait: ListingTrait {
|
|||
let fut: Pin<Box<dyn Future<Output = Result<()>> + Send + 'static>> =
|
||||
Box::pin(async move {
|
||||
let cl = async move {
|
||||
use melib::backends::mbox::MboxMetadata;
|
||||
let bytes: Vec<Vec<u8>> = try_join_all(futures?).await?;
|
||||
let envs: Vec<_> = envs_to_set
|
||||
.iter()
|
||||
|
@ -366,22 +367,37 @@ pub trait MailListingTrait: ListingTrait {
|
|||
.collect();
|
||||
let mut file = std::io::BufWriter::new(std::fs::File::create(&path_)?);
|
||||
let mut iter = envs.iter().zip(bytes.into_iter());
|
||||
let tags_lck = collection.tag_index.read().unwrap();
|
||||
if let Some((env, ref bytes)) = iter.next() {
|
||||
let tags: Vec<&str> = env
|
||||
.labels()
|
||||
.iter()
|
||||
.filter_map(|h| tags_lck.get(h).map(|s| s.as_str()))
|
||||
.collect();
|
||||
format.append(
|
||||
&mut file,
|
||||
bytes.as_slice(),
|
||||
env.from().get(0),
|
||||
Some(env.date()),
|
||||
(env.flags(), tags),
|
||||
MboxMetadata::CClient,
|
||||
true,
|
||||
false,
|
||||
)?;
|
||||
}
|
||||
for (env, bytes) in iter {
|
||||
let tags: Vec<&str> = env
|
||||
.labels()
|
||||
.iter()
|
||||
.filter_map(|h| tags_lck.get(h).map(|s| s.as_str()))
|
||||
.collect();
|
||||
format.append(
|
||||
&mut file,
|
||||
bytes.as_slice(),
|
||||
env.from().get(0),
|
||||
Some(env.date()),
|
||||
(env.flags(), tags),
|
||||
MboxMetadata::CClient,
|
||||
false,
|
||||
false,
|
||||
)?;
|
||||
|
|
Loading…
Reference in New Issue