Clippy fixes

pull/211/head
Manos Pitsidianakis 2023-04-30 19:39:41 +03:00
parent 3a02b6fb80
commit b1a7188771
Signed by: Manos Pitsidianakis
GPG Key ID: 7729C7707F7E09D0
136 changed files with 4642 additions and 3388 deletions

View File

@ -37,14 +37,9 @@ fn main() {
]);
#[cfg(feature = "cli-docs")]
{
use flate2::Compression;
use flate2::GzBuilder;
use flate2::{Compression, GzBuilder};
const MANDOC_OPTS: &[&str] = &["-T", "utf8", "-I", "os=Generated by mandoc(1)"];
use std::env;
use std::fs::File;
use std::io::prelude::*;
use std::path::Path;
use std::process::Command;
use std::{env, fs::File, io::prelude::*, path::Path, process::Command};
let out_dir = env::var("OUT_DIR").unwrap();
let mut out_dir_path = Path::new(&out_dir).to_path_buf();
@ -57,7 +52,8 @@ fn main() {
.output()
.or_else(|_| Command::new("man").arg("-l").arg(filepath).output())
.expect(
"could not execute `mandoc` or `man`. If the binaries are not available in the PATH, disable `cli-docs` feature to be able to continue compilation.",
"could not execute `mandoc` or `man`. If the binaries are not available in \
the PATH, disable `cli-docs` feature to be able to continue compilation.",
);
let file = File::create(&out_dir_path).unwrap_or_else(|err| {

View File

@ -19,9 +19,11 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use std::fs::File;
use std::io::prelude::*;
use std::process::{Command, Stdio};
use std::{
fs::File,
io::prelude::*,
process::{Command, Stdio},
};
use quote::{format_ident, quote};
@ -29,7 +31,8 @@ use quote::{format_ident, quote};
pub fn override_derive(filenames: &[(&str, &str)]) {
let mut output_file =
File::create("src/conf/overrides.rs").expect("Unable to open output file");
let mut output_string = r##"/*
let mut output_string = r##"// @generated
/*
* meli - conf/overrides.rs
*
* Copyright 2020 Manos Pitsidianakis
@ -60,7 +63,7 @@ use super::*;
'file_loop: for (filename, ident) in filenames {
println!("cargo:rerun-if-changed={}", filename);
let mut file = File::open(&filename)
let mut file = File::open(filename)
.unwrap_or_else(|err| panic!("Unable to open file `{}` {}", filename, err));
let mut src = String::new();

View File

@ -29,11 +29,12 @@ fn main() -> Result<(), std::io::Error> {
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed={}", MOD_PATH);
/* Line break tables */
use std::fs::File;
use std::io::prelude::*;
use std::io::BufReader;
use std::path::Path;
use std::process::{Command, Stdio};
use std::{
fs::File,
io::{prelude::*, BufReader},
path::Path,
process::{Command, Stdio},
};
const LINE_BREAK_TABLE_URL: &str =
"http://www.unicode.org/Public/UCD/latest/ucd/LineBreak.txt";
/* Grapheme width tables */
@ -52,7 +53,7 @@ fn main() -> Result<(), std::io::Error> {
std::process::exit(0);
}
let mut child = Command::new("curl")
.args(&["-o", "-", LINE_BREAK_TABLE_URL])
.args(["-o", "-", LINE_BREAK_TABLE_URL])
.stdout(Stdio::piped())
.stdin(Stdio::null())
.stderr(Stdio::inherit())
@ -69,7 +70,8 @@ fn main() -> Result<(), std::io::Error> {
let tokens: &str = line.split_whitespace().next().unwrap();
let semicolon_idx: usize = tokens.chars().position(|c| c == ';').unwrap();
/* LineBreak.txt list is ascii encoded so we can assume each char takes one byte: */
/* LineBreak.txt list is ascii encoded so we can assume each char takes one
* byte: */
let chars_str: &str = &tokens[..semicolon_idx];
let mut codepoint_iter = chars_str.split("..");
@ -87,21 +89,21 @@ fn main() -> Result<(), std::io::Error> {
child.wait()?;
let child = Command::new("curl")
.args(&["-o", "-", UNICODE_DATA_URL])
.args(["-o", "-", UNICODE_DATA_URL])
.stdout(Stdio::piped())
.output()?;
let unicode_data = String::from_utf8_lossy(&child.stdout);
let child = Command::new("curl")
.args(&["-o", "-", EAW_URL])
.args(["-o", "-", EAW_URL])
.stdout(Stdio::piped())
.output()?;
let eaw_data = String::from_utf8_lossy(&child.stdout);
let child = Command::new("curl")
.args(&["-o", "-", EMOJI_DATA_URL])
.args(["-o", "-", EMOJI_DATA_URL])
.stdout(Stdio::piped())
.output()?;
@ -198,13 +200,13 @@ fn main() -> Result<(), std::io::Error> {
}
// Apply the following special cases:
// - The unassigned code points in the following blocks default to "W":
// CJK Unified Ideographs Extension A: U+3400..U+4DBF
// CJK Unified Ideographs: U+4E00..U+9FFF
// CJK Compatibility Ideographs: U+F900..U+FAFF
// - All undesignated code points in Planes 2 and 3, whether inside or
// outside of allocated blocks, default to "W":
// Plane 2: U+20000..U+2FFFD
// Plane 3: U+30000..U+3FFFD
// - CJK Unified Ideographs Extension A: U+3400..U+4DBF
// - CJK Unified Ideographs: U+4E00..U+9FFF
// - CJK Compatibility Ideographs: U+F900..U+FAFF
// - All undesignated code points in Planes 2 and 3, whether inside or outside
// of allocated blocks, default to "W":
// - Plane 2: U+20000..U+2FFFD
// - Plane 3: U+30000..U+3FFFD
const WIDE_RANGES: [(usize, usize); 5] = [
(0x3400, 0x4DBF),
(0x4E00, 0x9FFF),
@ -245,12 +247,12 @@ fn main() -> Result<(), std::io::Error> {
}
use std::str::FromStr;
let mut v = comment.trim().split_whitespace().next().unwrap();
let mut v = comment.split_whitespace().next().unwrap();
if v.starts_with('E') {
v = &v[1..];
}
if v.as_bytes()
.get(0)
.first()
.map(|c| !c.is_ascii_digit())
.unwrap_or(true)
{
@ -325,7 +327,7 @@ fn main() -> Result<(), std::io::Error> {
}
}
let mut file = File::create(&mod_path)?;
let mut file = File::create(mod_path)?;
file.write_all(
br#"/*
* meli - text_processing crate.

View File

@ -24,12 +24,14 @@ pub mod vcard;
pub mod mutt;
use crate::datetime::{self, UnixTimestamp};
use crate::parsec::Parser;
use std::collections::HashMap;
use std::{collections::HashMap, ops::Deref};
use uuid::Uuid;
use std::ops::Deref;
use crate::{
datetime::{self, UnixTimestamp},
parsec::Parser,
};
#[derive(Hash, Debug, PartialEq, Eq, Clone, Copy, Deserialize, Serialize)]
#[serde(from = "String")]
@ -85,7 +87,8 @@ pub struct Card {
last_edited: UnixTimestamp,
extra_properties: HashMap<String, String>,
/// If true, we can't make any changes because we do not manage this resource.
/// If true, we can't make any changes because we do not manage this
/// resource.
external_resource: bool,
}

View File

@ -20,11 +20,11 @@
*/
//! # Mutt contact formats
//!
use std::collections::VecDeque;
use super::*;
use crate::parsec::{is_not, map_res, match_literal_anycase, prefix, Parser};
use std::collections::VecDeque;
//alias <nickname> [ <long name> ] <address>
// From mutt doc:

View File

@ -27,11 +27,13 @@
//! - Version 4 [RFC 6350: vCard Format Specification](https://datatracker.ietf.org/doc/rfc6350/)
//! - Parameter escaping [RFC 6868 Parameter Value Encoding in iCalendar and vCard](https://datatracker.ietf.org/doc/rfc6868/)
use std::{collections::HashMap, convert::TryInto};
use super::*;
use crate::error::{Error, Result};
use crate::parsec::{match_literal_anycase, one_or_more, peek, prefix, take_until, Parser};
use std::collections::HashMap;
use std::convert::TryInto;
use crate::{
error::{Error, Result},
parsec::{match_literal_anycase, one_or_more, peek, prefix, take_until, Parser},
};
/* Supported vcard versions */
pub trait VCardVersion: core::fmt::Debug {}
@ -86,7 +88,11 @@ impl CardDeserializer {
input = if (!input.starts_with(HEADER_CRLF) || !input.ends_with(FOOTER_CRLF))
&& (!input.starts_with(HEADER_LF) || !input.ends_with(FOOTER_LF))
{
return Err(Error::new(format!("Error while parsing vcard: input does not start or end with correct header and footer. input is:\n{:?}", input)));
return Err(Error::new(format!(
"Error while parsing vcard: input does not start or end with correct header and \
footer. input is:\n{:?}",
input
)));
} else if input.starts_with(HEADER_CRLF) {
&input[HEADER_CRLF.len()..input.len() - FOOTER_CRLF.len()]
} else {

View File

@ -36,35 +36,41 @@ pub mod jmap;
pub mod maildir;
#[cfg(feature = "mbox_backend")]
pub mod mbox;
use std::{
any::Any,
borrow::Cow,
collections::{BTreeSet, HashMap},
fmt,
fmt::Debug,
future::Future,
ops::Deref,
pin::Pin,
sync::{Arc, RwLock},
};
use futures::stream::Stream;
#[cfg(feature = "imap_backend")]
pub use self::imap::ImapType;
#[cfg(feature = "imap_backend")]
pub use self::nntp::NntpType;
use crate::conf::AccountSettings;
use crate::error::{Error, ErrorKind, Result};
#[cfg(feature = "maildir_backend")]
use self::maildir::MaildirType;
#[cfg(feature = "mbox_backend")]
use self::mbox::MboxType;
#[cfg(feature = "imap_backend")]
pub use self::nntp::NntpType;
use super::email::{Envelope, EnvelopeHash, Flag};
use futures::stream::Stream;
use std::any::Any;
use std::borrow::Cow;
use std::collections::BTreeSet;
use std::collections::HashMap;
use std::fmt;
use std::fmt::Debug;
use std::future::Future;
use std::ops::Deref;
use std::pin::Pin;
use std::sync::{Arc, RwLock};
use crate::{
conf::AccountSettings,
error::{Error, ErrorKind, Result},
};
#[macro_export]
macro_rules! get_path_hash {
($path:expr) => {{
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::{
collections::hash_map::DefaultHasher,
hash::{Hash, Hasher},
};
let mut hasher = DefaultHasher::new();
$path.hash(&mut hasher);
hasher.finish()
@ -97,10 +103,13 @@ impl Default for Backends {
}
#[cfg(feature = "notmuch_backend")]
pub const NOTMUCH_ERROR_MSG: &str =
"libnotmuch5 was not found in your system. Make sure it is installed and in the library paths. For a custom file path, use `library_file_path` setting in your notmuch account.\n";
pub const NOTMUCH_ERROR_MSG: &str = "libnotmuch5 was not found in your system. Make sure it is \
installed and in the library paths. For a custom file path, \
use `library_file_path` setting in your notmuch account.\n";
#[cfg(not(feature = "notmuch_backend"))]
pub const NOTMUCH_ERROR_MSG: &str = "this version of meli is not compiled with notmuch support. Use an appropriate version and make sure libnotmuch5 is installed and in the library paths.\n";
pub const NOTMUCH_ERROR_MSG: &str = "this version of meli is not compiled with notmuch support. \
Use an appropriate version and make sure libnotmuch5 is \
installed and in the library paths.\n";
#[cfg(not(feature = "notmuch_backend"))]
pub const NOTMUCH_ERROR_DETAILS: &str = "";
@ -432,13 +441,14 @@ pub trait MailBackend: ::std::fmt::Debug + Send + Sync {
}
}
/// A `BackendOp` manages common operations for the various mail backends. They only live for the
/// duration of the operation. They are generated by the `operation` method of `Mailbackend` trait.
/// A `BackendOp` manages common operations for the various mail backends. They
/// only live for the duration of the operation. They are generated by the
/// `operation` method of `Mailbackend` trait.
///
/// # Motivation
///
/// We need a way to do various operations on individual mails regardless of what backend they come
/// from (eg local or imap).
/// We need a way to do various operations on individual mails regardless of
/// what backend they come from (eg local or imap).
///
/// # Creation
/// ```ignore
@ -474,8 +484,8 @@ pub trait BackendOp: ::std::fmt::Debug + ::std::marker::Send {
/// Wrapper for BackendOps that are to be set read-only.
///
/// Warning: Backend implementations may still cause side-effects (for example IMAP can set the
/// Seen flag when fetching an envelope)
/// Warning: Backend implementations may still cause side-effects (for example
/// IMAP can set the Seen flag when fetching an envelope)
#[derive(Debug)]
pub struct ReadOnlyOp {
op: Box<dyn BackendOp>,

View File

@ -36,26 +36,29 @@ use cache::{ImapCacheReset, ModSequence};
pub mod managesieve;
mod untagged;
use crate::backends::{
RefreshEventKind::{self, *},
*,
use std::{
collections::{hash_map::DefaultHasher, BTreeSet, HashMap, HashSet},
convert::TryFrom,
hash::Hasher,
pin::Pin,
str::FromStr,
sync::{Arc, Mutex},
time::{Duration, SystemTime},
};
use crate::collection::Collection;
use crate::conf::AccountSettings;
use crate::connections::timeout;
use crate::email::{parser::BytesExt, *};
use crate::error::{Error, Result, ResultIntoError};
use futures::lock::Mutex as FutureMutex;
use futures::stream::Stream;
use std::collections::hash_map::DefaultHasher;
use std::collections::{BTreeSet, HashMap, HashSet};
use std::convert::TryFrom;
use std::hash::Hasher;
use std::pin::Pin;
use std::str::FromStr;
use std::sync::{Arc, Mutex};
use std::time::{Duration, SystemTime};
use futures::{lock::Mutex as FutureMutex, stream::Stream};
use crate::{
backends::{
RefreshEventKind::{self, *},
*,
},
collection::Collection,
conf::AccountSettings,
connections::timeout,
email::{parser::BytesExt, *},
error::{Error, Result, ResultIntoError},
};
pub type ImapNum = usize;
pub type UID = ImapNum;
@ -340,7 +343,8 @@ impl MailBackend for ImapType {
cache_handle,
};
/* do this in a closure to prevent recursion limit error in async_stream macro */
/* do this in a closure to prevent recursion limit error in async_stream
* macro */
let prepare_cl = |f: &ImapMailbox| {
f.set_warm(true);
if let Ok(mut exists) = f.exists.lock() {
@ -526,15 +530,15 @@ impl MailBackend for ImapType {
}
fn operation(&self, hash: EnvelopeHash) -> Result<Box<dyn BackendOp>> {
let (uid, mailbox_hash) = if let Some(v) =
self.uid_store.hash_index.lock().unwrap().get(&hash)
{
*v
} else {
return Err(Error::new(
"Message not found in local cache, it might have been deleted before you requested it."
let (uid, mailbox_hash) =
if let Some(v) = self.uid_store.hash_index.lock().unwrap().get(&hash) {
*v
} else {
return Err(Error::new(
"Message not found in local cache, it might have been deleted before you \
requested it.",
));
};
};
Ok(Box::new(ImapOp::new(
uid,
mailbox_hash,
@ -749,8 +753,20 @@ impl MailBackend for ImapType {
cmd.push_str("\\Draft ");
}
Ok(_) => {
crate::log(format!("Application error: more than one flag bit set in set_flags: {:?}", flags), crate::ERROR);
return Err(Error::new(format!("Application error: more than one flag bit set in set_flags: {:?}", flags)).set_kind(crate::ErrorKind::Bug));
crate::log(
format!(
"Application error: more than one flag bit set in \
set_flags: {:?}",
flags
),
crate::ERROR,
);
return Err(Error::new(format!(
"Application error: more than one flag bit set in set_flags: \
{:?}",
flags
))
.set_kind(crate::ErrorKind::Bug));
}
Err(tag) => {
let hash = TagHash::from_bytes(tag.as_bytes());
@ -812,13 +828,17 @@ impl MailBackend for ImapType {
Ok(_) => {
crate::log(
format!(
"Application error: more than one flag bit set in set_flags: {:?}", flags
),
"Application error: more than one flag bit set in \
set_flags: {:?}",
flags
),
crate::ERROR,
);
return Err(Error::new(format!(
"Application error: more than one flag bit set in set_flags: {:?}", flags
)));
"Application error: more than one flag bit set in set_flags: \
{:?}",
flags
)));
}
Err(tag) => {
cmd.push_str(tag);
@ -892,16 +912,17 @@ impl MailBackend for ImapType {
Ok(Box::pin(async move {
/* Must transform path to something the IMAP server will accept
*
* Each root mailbox has a hierarchy delimeter reported by the LIST entry. All paths
* must use this delimeter to indicate children of this mailbox.
* Each root mailbox has a hierarchy delimeter reported by the LIST entry.
* All paths must use this delimeter to indicate children of this
* mailbox.
*
* A new root mailbox should have the default delimeter, which can be found out by issuing
* an empty LIST command as described in RFC3501:
* A new root mailbox should have the default delimeter, which can be found
* out by issuing an empty LIST command as described in RFC3501:
* C: A101 LIST "" ""
* S: * LIST (\Noselect) "/" ""
*
* The default delimiter for us is '/' just like UNIX paths. I apologise if this
* decision is unpleasant for you.
* The default delimiter for us is '/' just like UNIX paths. I apologise if
* this decision is unpleasant for you.
*/
{
@ -924,8 +945,8 @@ impl MailBackend for ImapType {
}
}
/* FIXME Do not try to CREATE a sub-mailbox in a mailbox that has the \Noinferiors
* flag set. */
/* FIXME Do not try to CREATE a sub-mailbox in a mailbox
* that has the \Noinferiors flag set. */
}
let mut response = Vec::with_capacity(8 * 1024);
@ -950,7 +971,17 @@ impl MailBackend for ImapType {
ret?;
let new_hash = MailboxHash::from_bytes(path.as_str().as_bytes());
uid_store.mailboxes.lock().await.clear();
Ok((new_hash, new_mailbox_fut?.await.map_err(|err| Error::new(format!("Mailbox create was succesful (returned `{}`) but listing mailboxes afterwards returned `{}`", String::from_utf8_lossy(&response), err)))?))
Ok((
new_hash,
new_mailbox_fut?.await.map_err(|err| {
Error::new(format!(
"Mailbox create was succesful (returned `{}`) but listing mailboxes \
afterwards returned `{}`",
String::from_utf8_lossy(&response),
err
))
})?,
))
}))
}
@ -970,7 +1001,12 @@ impl MailBackend for ImapType {
imap_path = mailboxes[&mailbox_hash].imap_path().to_string();
let permissions = mailboxes[&mailbox_hash].permissions();
if !permissions.delete_mailbox {
return Err(Error::new(format!("You do not have permission to delete `{}`. Set permissions for this mailbox are {}", mailboxes[&mailbox_hash].name(), permissions)));
return Err(Error::new(format!(
"You do not have permission to delete `{}`. Set permissions for this \
mailbox are {}",
mailboxes[&mailbox_hash].name(),
permissions
)));
}
}
let mut response = Vec::with_capacity(8 * 1024);
@ -998,7 +1034,15 @@ impl MailBackend for ImapType {
let ret: Result<()> = ImapResponse::try_from(response.as_slice())?.into();
ret?;
uid_store.mailboxes.lock().await.clear();
new_mailbox_fut?.await.map_err(|err| format!("Mailbox delete was succesful (returned `{}`) but listing mailboxes afterwards returned `{}`", String::from_utf8_lossy(&response), err).into())
new_mailbox_fut?.await.map_err(|err| {
format!(
"Mailbox delete was succesful (returned `{}`) but listing mailboxes \
afterwards returned `{}`",
String::from_utf8_lossy(&response),
err
)
.into()
})
}))
}
@ -1064,7 +1108,12 @@ impl MailBackend for ImapType {
let mailboxes = uid_store.mailboxes.lock().await;
let permissions = mailboxes[&mailbox_hash].permissions();
if !permissions.delete_mailbox {
return Err(Error::new(format!("You do not have permission to rename mailbox `{}` (rename is equivalent to delete + create). Set permissions for this mailbox are {}", mailboxes[&mailbox_hash].name(), permissions)));
return Err(Error::new(format!(
"You do not have permission to rename mailbox `{}` (rename is equivalent \
to delete + create). Set permissions for this mailbox are {}",
mailboxes[&mailbox_hash].name(),
permissions
)));
}
if mailboxes[&mailbox_hash].separator != b'/' {
new_path = new_path.replace(
@ -1089,7 +1138,14 @@ impl MailBackend for ImapType {
let ret: Result<()> = ImapResponse::try_from(response.as_slice())?.into();
ret?;
uid_store.mailboxes.lock().await.clear();
new_mailbox_fut?.await.map_err(|err| format!("Mailbox rename was succesful (returned `{}`) but listing mailboxes afterwards returned `{}`", String::from_utf8_lossy(&response), err))?;
new_mailbox_fut?.await.map_err(|err| {
format!(
"Mailbox rename was succesful (returned `{}`) but listing mailboxes \
afterwards returned `{}`",
String::from_utf8_lossy(&response),
err
)
})?;
Ok(BackendMailbox::clone(
&uid_store.mailboxes.lock().await[&new_hash],
))
@ -1107,7 +1163,12 @@ impl MailBackend for ImapType {
let mailboxes = uid_store.mailboxes.lock().await;
let permissions = mailboxes[&mailbox_hash].permissions();
if !permissions.change_permissions {
return Err(Error::new(format!("You do not have permission to change permissions for mailbox `{}`. Set permissions for this mailbox are {}", mailboxes[&mailbox_hash].name(), permissions)));
return Err(Error::new(format!(
"You do not have permission to change permissions for mailbox `{}`. Set \
permissions for this mailbox are {}",
mailboxes[&mailbox_hash].name(),
permissions
)));
}
Err(Error::new("Unimplemented."))
@ -1262,7 +1323,8 @@ impl ImapType {
if use_oauth2 && !s.extra.contains_key("server_password_command") {
return Err(Error::new(format!(
"({}) `use_oauth2` use requires `server_password_command` set with a command that returns an OAUTH2 token. Consult documentation for guidance.",
"({}) `use_oauth2` use requires `server_password_command` set with a command that \
returns an OAUTH2 token. Consult documentation for guidance.",
s.name,
)));
}
@ -1524,14 +1586,16 @@ impl ImapType {
if !s.extra.contains_key("server_password_command") {
if use_oauth2 {
return Err(Error::new(format!(
"({}) `use_oauth2` use requires `server_password_command` set with a command that returns an OAUTH2 token. Consult documentation for guidance.",
"({}) `use_oauth2` use requires `server_password_command` set with a command \
that returns an OAUTH2 token. Consult documentation for guidance.",
s.name,
)));
}
get_conf_val!(s["server_password"])?;
} else if s.extra.contains_key("server_password") {
return Err(Error::new(format!(
"Configuration error ({}): both server_password and server_password_command are set, cannot choose",
"Configuration error ({}): both server_password and server_password_command are \
set, cannot choose",
s.name.as_str(),
)));
}
@ -1541,7 +1605,8 @@ impl ImapType {
let use_starttls = get_conf_val!(s["use_starttls"], false)?;
if !use_tls && use_starttls {
return Err(Error::new(format!(
"Configuration error ({}): incompatible use_tls and use_starttls values: use_tls = false, use_starttls = true",
"Configuration error ({}): incompatible use_tls and use_starttls values: use_tls \
= false, use_starttls = true",
s.name.as_str(),
)));
}
@ -1565,7 +1630,8 @@ impl ImapType {
#[cfg(not(feature = "deflate_compression"))]
if s.extra.contains_key("use_deflate") {
return Err(Error::new(format!(
"Configuration error ({}): setting `use_deflate` is set but this version of meli isn't compiled with DEFLATE support.",
"Configuration error ({}): setting `use_deflate` is set but this version of meli \
isn't compiled with DEFLATE support.",
s.name.as_str(),
)));
}
@ -1578,8 +1644,10 @@ impl ImapType {
let diff = extra_keys.difference(&keys).collect::<Vec<&&str>>();
if !diff.is_empty() {
return Err(Error::new(format!(
"Configuration error ({}): the following flags are set but are not recognized: {:?}.",
s.name.as_str(), diff
"Configuration error ({}): the following flags are set but are not recognized: \
{:?}.",
s.name.as_str(),
diff
)));
}
Ok(())
@ -1658,7 +1726,14 @@ async fn fetch_hlpr(state: &mut FetchState) -> Result<Vec<Envelope>> {
/* Try resetting the database */
if let Some(ref mut cache_handle) = state.cache_handle {
if let Err(err) = cache_handle.reset() {
crate::log(format!("IMAP cache error: could not reset cache for {}. Reason: {}", state.uid_store.account_name, err), crate::ERROR);
crate::log(
format!(
"IMAP cache error: could not reset cache for {}. Reason: \
{}",
state.uid_store.account_name, err
),
crate::ERROR,
);
}
}
state.stage = FetchStage::InitialFresh;
@ -1743,11 +1818,14 @@ async fn fetch_hlpr(state: &mut FetchState) -> Result<Vec<Envelope>> {
if max_uid_left > 0 {
debug!("{} max_uid_left= {}", mailbox_hash, max_uid_left);
let command = if max_uid_left == 1 {
"UID FETCH 1 (UID FLAGS ENVELOPE BODY.PEEK[HEADER.FIELDS (REFERENCES)] BODYSTRUCTURE)".to_string()
"UID FETCH 1 (UID FLAGS ENVELOPE BODY.PEEK[HEADER.FIELDS (REFERENCES)] \
BODYSTRUCTURE)"
.to_string()
} else {
format!(
"UID FETCH {}:{} (UID FLAGS ENVELOPE BODY.PEEK[HEADER.FIELDS (REFERENCES)] BODYSTRUCTURE)",
std::cmp::max(max_uid_left.saturating_sub(chunk_size), 1),
"UID FETCH {}:{} (UID FLAGS ENVELOPE BODY.PEEK[HEADER.FIELDS \
(REFERENCES)] BODYSTRUCTURE)",
std::cmp::max(max_uid_left.saturating_sub(chunk_size), 1),
max_uid_left
)
};

View File

@ -21,12 +21,13 @@
use super::*;
mod sync;
use std::convert::TryFrom;
use crate::{
backends::MailboxHash,
email::{Envelope, EnvelopeHash},
error::*,
};
use std::convert::TryFrom;
#[derive(Debug, PartialEq, Hash, Eq, Ord, PartialOrd, Copy, Clone)]
pub struct ModSequence(pub std::num::NonZeroU64);
@ -105,10 +106,11 @@ pub use sqlite3_m::*;
#[cfg(feature = "sqlite3")]
mod sqlite3_m {
use super::*;
use crate::sqlite3::rusqlite::types::{
FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput,
use crate::sqlite3::{
self,
rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput},
DatabaseDescription,
};
use crate::sqlite3::{self, DatabaseDescription};
type Sqlite3UID = i32;
@ -287,23 +289,45 @@ mod sqlite3_m {
})?;
if let Some(Ok(highestmodseq)) = select_response.highestmodseq {
self.connection.execute(
"INSERT OR IGNORE INTO mailbox (uidvalidity, flags, highestmodseq, mailbox_hash) VALUES (?1, ?2, ?3, ?4)",
sqlite3::params![select_response.uidvalidity as Sqlite3UID, select_response.flags.1.iter().map(|s| s.as_str()).collect::<Vec<&str>>().join("\0").as_bytes(), highestmodseq, mailbox_hash],
)
.chain_err_summary(|| {
format!(
"Could not insert uidvalidity {} in header_cache of account {}",
select_response.uidvalidity, self.uid_store.account_name
)
})?;
self.connection
.execute(
"INSERT OR IGNORE INTO mailbox (uidvalidity, flags, highestmodseq, \
mailbox_hash) VALUES (?1, ?2, ?3, ?4)",
sqlite3::params![
select_response.uidvalidity as Sqlite3UID,
select_response
.flags
.1
.iter()
.map(|s| s.as_str())
.collect::<Vec<&str>>()
.join("\0")
.as_bytes(),
highestmodseq,
mailbox_hash
],
)
.chain_err_summary(|| {
format!(
"Could not insert uidvalidity {} in header_cache of account {}",
select_response.uidvalidity, self.uid_store.account_name
)
})?;
} else {
self.connection
.execute(
"INSERT OR IGNORE INTO mailbox (uidvalidity, flags, mailbox_hash) VALUES (?1, ?2, ?3)",
"INSERT OR IGNORE INTO mailbox (uidvalidity, flags, mailbox_hash) VALUES \
(?1, ?2, ?3)",
sqlite3::params![
select_response.uidvalidity as Sqlite3UID,
select_response.flags.1.iter().map(|s| s.as_str()).collect::<Vec<&str>>().join("\0").as_bytes(),
select_response
.flags
.1
.iter()
.map(|s| s.as_str())
.collect::<Vec<&str>>()
.join("\0")
.as_bytes(),
mailbox_hash
],
)
@ -463,9 +487,24 @@ mod sqlite3_m {
{
max_uid = std::cmp::max(max_uid, *uid);
tx.execute(
"INSERT OR REPLACE INTO envelopes (hash, uid, mailbox_hash, modsequence, envelope) VALUES (?1, ?2, ?3, ?4, ?5)",
sqlite3::params![envelope.hash(), *uid as Sqlite3UID, mailbox_hash, modseq, &envelope],
).chain_err_summary(|| format!("Could not insert envelope {} {} in header_cache of account {}", envelope.message_id(), envelope.hash(), uid_store.account_name))?;
"INSERT OR REPLACE INTO envelopes (hash, uid, mailbox_hash, modsequence, \
envelope) VALUES (?1, ?2, ?3, ?4, ?5)",
sqlite3::params![
envelope.hash(),
*uid as Sqlite3UID,
mailbox_hash,
modseq,
&envelope
],
)
.chain_err_summary(|| {
format!(
"Could not insert envelope {} {} in header_cache of account {}",
envelope.message_id(),
envelope.hash(),
uid_store.account_name
)
})?;
}
}
tx.commit()?;
@ -523,15 +562,17 @@ mod sqlite3_m {
env.tags_mut()
.extend(tags.iter().map(|t| TagHash::from_bytes(t.as_bytes())));
tx.execute(
"UPDATE envelopes SET envelope = ?1 WHERE mailbox_hash = ?2 AND uid = ?3;",
sqlite3::params![&env, mailbox_hash, *uid as Sqlite3UID],
)
.chain_err_summary(|| {
format!(
"Could not update envelope {} uid {} from mailbox {} account {}",
env_hash, *uid, mailbox_hash, uid_store.account_name
)
})?;
"UPDATE envelopes SET envelope = ?1 WHERE mailbox_hash = ?2 AND \
uid = ?3;",
sqlite3::params![&env, mailbox_hash, *uid as Sqlite3UID],
)
.chain_err_summary(|| {
format!(
"Could not update envelope {} uid {} from mailbox {} account \
{}",
env_hash, *uid, mailbox_hash, uid_store.account_name
)
})?;
uid_store
.envelopes
.lock()
@ -563,8 +604,9 @@ mod sqlite3_m {
let mut ret: Vec<(UID, Envelope, Option<ModSequence>)> = match identifier {
Ok(uid) => {
let mut stmt = self.connection.prepare(
"SELECT uid, envelope, modsequence FROM envelopes WHERE mailbox_hash = ?1 AND uid = ?2;",
)?;
"SELECT uid, envelope, modsequence FROM envelopes WHERE mailbox_hash = ?1 \
AND uid = ?2;",
)?;
let x = stmt
.query_map(sqlite3::params![mailbox_hash, uid as Sqlite3UID], |row| {
@ -579,8 +621,9 @@ mod sqlite3_m {
}
Err(env_hash) => {
let mut stmt = self.connection.prepare(
"SELECT uid, envelope, modsequence FROM envelopes WHERE mailbox_hash = ?1 AND hash = ?2;",
)?;
"SELECT uid, envelope, modsequence FROM envelopes WHERE mailbox_hash = ?1 \
AND hash = ?2;",
)?;
let x = stmt
.query_map(sqlite3::params![mailbox_hash, env_hash], |row| {

View File

@ -130,7 +130,8 @@ impl ImapConnection {
// 2. tag1 UID FETCH <lastseenuid+1>:* <descriptors>
self.send_command(
format!(
"UID FETCH {}:* (UID FLAGS ENVELOPE BODY.PEEK[HEADER.FIELDS (REFERENCES)] BODYSTRUCTURE)",
"UID FETCH {}:* (UID FLAGS ENVELOPE BODY.PEEK[HEADER.FIELDS (REFERENCES)] \
BODYSTRUCTURE)",
max_uid + 1
)
.as_bytes(),
@ -375,9 +376,9 @@ impl ImapConnection {
// client MUST
// * empty the local cache of that mailbox;
// * "forget" the cached HIGHESTMODSEQ value for the mailbox;
// * remove any pending "actions" that refer to UIDs in that
// mailbox (note that this doesn't affect actions performed on
// client-generated fake UIDs; see Section 5); and
// * remove any pending "actions" that refer to UIDs in that mailbox (note
// that this doesn't affect actions performed on client-generated fake UIDs;
// see Section 5); and
// * skip steps 1b and 2-II;
cache_handle.clear(mailbox_hash, &select_response)?;
return Ok(None);
@ -398,9 +399,9 @@ impl ImapConnection {
let new_highestmodseq = select_response.highestmodseq.unwrap().unwrap();
let mut refresh_events = vec![];
// 1b) Check the mailbox HIGHESTMODSEQ.
// If the cached value is the same as the one returned by the server, skip fetching
// message flags on step 2-II, i.e., the client only has to find out which messages got
// expunged.
// If the cached value is the same as the one returned by the server, skip
// fetching message flags on step 2-II, i.e., the client only has to
// find out which messages got expunged.
if cached_highestmodseq != new_highestmodseq {
/* Cache is synced, only figure out which messages got expunged */
@ -415,7 +416,8 @@ impl ImapConnection {
// 2. tag1 UID FETCH <lastseenuid+1>:* <descriptors>
self.send_command(
format!(
"UID FETCH {}:* (UID FLAGS ENVELOPE BODY.PEEK[HEADER.FIELDS (REFERENCES)] BODYSTRUCTURE) (CHANGEDSINCE {})",
"UID FETCH {}:* (UID FLAGS ENVELOPE BODY.PEEK[HEADER.FIELDS (REFERENCES)] \
BODYSTRUCTURE) (CHANGEDSINCE {})",
cached_max_uid + 1,
cached_highestmodseq,
)
@ -571,8 +573,8 @@ impl ImapConnection {
.insert(mailbox_hash, Ok(new_highestmodseq));
}
let mut valid_envs = BTreeSet::default();
// This should be UID SEARCH 1:<maxuid> but it's difficult to compare to cached UIDs at the
// point of calling this function
// This should be UID SEARCH 1:<maxuid> but it's difficult to compare to cached
// UIDs at the point of calling this function
self.send_command(b"UID SEARCH ALL").await?;
self.read_response(&mut response, RequiredResponses::SEARCH)
.await?;
@ -614,7 +616,8 @@ impl ImapConnection {
Ok(Some(payload.into_iter().map(|(_, env)| env).collect()))
}
//rfc7162_Quick Flag Changes Resynchronization (CONDSTORE)_and Quick Mailbox Resynchronization (QRESYNC)
//rfc7162_Quick Flag Changes Resynchronization (CONDSTORE)_and Quick Mailbox
// Resynchronization (QRESYNC)
pub async fn resync_condstoreqresync(
&mut self,
_cache_handle: Box<dyn ImapCache>,
@ -634,8 +637,8 @@ impl ImapConnection {
)
};
/* first SELECT the mailbox to get READ/WRITE permissions (because EXAMINE only
* returns READ-ONLY for both cases) */
/* first SELECT the mailbox to get READ/WRITE permissions (because EXAMINE
* only returns READ-ONLY for both cases) */
let mut select_response = self
.select_mailbox(mailbox_hash, &mut response, true)
.await?

View File

@ -20,33 +20,38 @@
*/
use super::protocol_parser::{ImapLineSplit, ImapResponse, RequiredResponses, SelectResponse};
use crate::backends::{MailboxHash, RefreshEvent};
use crate::connections::{lookup_ipv4, timeout, Connection};
use crate::email::parser::BytesExt;
use crate::error::*;
use crate::{
backends::{MailboxHash, RefreshEvent},
connections::{lookup_ipv4, timeout, Connection},
email::parser::BytesExt,
error::*,
};
extern crate native_tls;
use std::{
collections::HashSet,
convert::TryFrom,
future::Future,
iter::FromIterator,
pin::Pin,
sync::Arc,
time::{Duration, Instant, SystemTime},
};
use futures::io::{AsyncReadExt, AsyncWriteExt};
use native_tls::TlsConnector;
pub use smol::Async as AsyncWrapper;
use std::collections::HashSet;
use std::convert::TryFrom;
use std::future::Future;
use std::iter::FromIterator;
use std::pin::Pin;
use std::sync::Arc;
use std::time::{Duration, Instant, SystemTime};
const IMAP_PROTOCOL_TIMEOUT: Duration = Duration::from_secs(60 * 28);
use super::protocol_parser;
use super::{Capabilities, ImapServerConf, UIDStore};
use super::{protocol_parser, Capabilities, ImapServerConf, UIDStore};
#[derive(Debug, Clone, Copy)]
pub enum SyncPolicy {
None,
///rfc4549 `Synch Ops for Disconnected IMAP4 Clients` <https://tools.ietf.org/html/rfc4549>
Basic,
///rfc7162 `IMAP Extensions: Quick Flag Changes Resynchronization (CONDSTORE) and Quick Mailbox Resynchronization (QRESYNC)`
///rfc7162 `IMAP Extensions: Quick Flag Changes Resynchronization
/// (CONDSTORE) and Quick Mailbox Resynchronization (QRESYNC)`
Condstore,
CondstoreQresync,
}
@ -144,13 +149,14 @@ impl ImapStream {
if let Some(timeout) = server_conf.timeout {
TcpStream::connect_timeout(&addr, timeout)?
} else {
TcpStream::connect(&addr)?
TcpStream::connect(addr)?
},
))?;
if server_conf.use_starttls {
let err_fn = || {
if server_conf.server_port == 993 {
"STARTTLS failed. Server port is set to 993, which normally uses TLS. Maybe try disabling use_starttls."
"STARTTLS failed. Server port is set to 993, which normally uses TLS. \
Maybe try disabling use_starttls."
} else {
"STARTTLS failed. Is the connection already encrypted?"
}
@ -246,7 +252,7 @@ impl ImapStream {
if let Some(timeout) = server_conf.timeout {
TcpStream::connect_timeout(&addr, timeout)?
} else {
TcpStream::connect(&addr)?
TcpStream::connect(addr)?
},
))?
};
@ -350,10 +356,14 @@ impl ImapStream {
.any(|cap| cap.eq_ignore_ascii_case(b"AUTH=XOAUTH2"))
{
return Err(Error::new(format!(
"Could not connect to {}: OAUTH2 is enabled but server did not return AUTH=XOAUTH2 capability. Returned capabilities were: {}",
&server_conf.server_hostname,
capabilities.iter().map(|capability|
String::from_utf8_lossy(capability).to_string()).collect::<Vec<String>>().join(" ")
"Could not connect to {}: OAUTH2 is enabled but server did not return \
AUTH=XOAUTH2 capability. Returned capabilities were: {}",
&server_conf.server_hostname,
capabilities
.iter()
.map(|capability| String::from_utf8_lossy(capability).to_string())
.collect::<Vec<String>>()
.join(" ")
)));
}
ret.send_command(
@ -414,8 +424,8 @@ impl ImapStream {
}
if capabilities.is_none() {
/* sending CAPABILITY after LOGIN automatically is an RFC recommendation, so check
* for lazy servers */
/* sending CAPABILITY after LOGIN automatically is an RFC recommendation, so
* check for lazy servers */
drop(capabilities);
ret.send_command(b"CAPABILITY").await?;
ret.read_response(&mut res).await.unwrap();
@ -648,7 +658,14 @@ impl ImapConnection {
| ImapResponse::Bad(code)
| ImapResponse::Preauth(code)
| ImapResponse::Bye(code) => {
crate::log(format!("Could not use COMPRESS=DEFLATE in account `{}`: server replied with `{}`", self.uid_store.account_name, code), crate::LoggingLevel::WARN);
crate::log(
format!(
"Could not use COMPRESS=DEFLATE in account `{}`: server \
replied with `{}`",
self.uid_store.account_name, code
),
crate::LoggingLevel::WARN,
);
}
ImapResponse::Ok(_) => {
let ImapStream {
@ -750,7 +767,7 @@ impl ImapConnection {
&required_responses
);*/
for l in response.split_rn() {
/*debug!("check line: {}", &l);*/
/* debug!("check line: {}", &l); */
if required_responses.check(l) || !self.process_untagged(l).await? {
ret.extend_from_slice(l);
}

View File

@ -19,13 +19,16 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use super::protocol_parser::SelectResponse;
use crate::backends::{
BackendMailbox, LazyCountSet, Mailbox, MailboxHash, MailboxPermissions, SpecialUsageMailbox,
};
use crate::error::*;
use std::sync::{Arc, Mutex, RwLock};
use super::protocol_parser::SelectResponse;
use crate::{
backends::{
BackendMailbox, LazyCountSet, Mailbox, MailboxHash, MailboxPermissions, SpecialUsageMailbox,
},
error::*,
};
#[derive(Debug, Default, Clone)]
pub struct ImapMailbox {
pub hash: MailboxHash,
@ -51,7 +54,8 @@ impl ImapMailbox {
&self.imap_path
}
/// Establish that mailbox contents have been fetched at least once during this execution
/// Establish that mailbox contents have been fetched at least once during
/// this execution
#[inline(always)]
pub fn set_warm(&self, new_value: bool) {
*self.warm.lock().unwrap() = new_value;

View File

@ -19,19 +19,25 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use super::{ImapConnection, ImapProtocol, ImapServerConf, UIDStore};
use crate::conf::AccountSettings;
use crate::email::parser::IResult;
use crate::error::{Error, Result};
use crate::get_conf_val;
use crate::imap::RequiredResponses;
use std::{
str::FromStr,
sync::{Arc, Mutex},
time::SystemTime,
};
use nom::{
branch::alt, bytes::complete::tag, combinator::map, multi::separated_list1,
sequence::separated_pair,
};
use std::str::FromStr;
use std::sync::{Arc, Mutex};
use std::time::SystemTime;
use super::{ImapConnection, ImapProtocol, ImapServerConf, UIDStore};
use crate::{
conf::AccountSettings,
email::parser::IResult,
error::{Error, Result},
get_conf_val,
imap::RequiredResponses,
};
pub struct ManageSieveConnection {
pub inner: ImapConnection,
@ -61,12 +67,17 @@ pub enum ManageSieveResponse<'a> {
}
mod parser {
use nom::{
bytes::complete::tag,
character::complete::crlf,
combinator::{iterator, map, opt},
};
pub use nom::{
bytes::complete::{is_not, tag_no_case},
sequence::{delimited, pair, preceded, terminated},
};
use super::*;
use nom::bytes::complete::tag;
pub use nom::bytes::complete::{is_not, tag_no_case};
use nom::character::complete::crlf;
use nom::combinator::{iterator, map, opt};
pub use nom::sequence::{delimited, pair, preceded, terminated};
pub fn sieve_name(input: &[u8]) -> IResult<&[u8], &[u8]> {
crate::backends::imap::protocol_parser::string_token(input)

View File

@ -19,13 +19,11 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use super::*;
use crate::backends::*;
use crate::email::*;
use crate::error::Error;
use std::sync::Arc;
use super::*;
use crate::{backends::*, email::*, error::Error};
/// `BackendOp` implementor for Imap
#[derive(Debug, Clone)]
pub struct ImapOp {

View File

@ -19,24 +19,28 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use super::*;
use crate::email::address::{Address, MailboxAddress};
use crate::email::parser::{
generic::{byte_in_range, byte_in_slice},
BytesExt, IResult,
};
use crate::error::ResultIntoError;
use std::{convert::TryFrom, str::FromStr};
use nom::{
branch::{alt, permutation},
bytes::complete::{is_a, is_not, tag, take, take_until, take_while},
character::complete::digit1,
character::is_digit,
character::{complete::digit1, is_digit},
combinator::{map, map_res, opt},
multi::{fold_many1, length_data, many0, many1, separated_list1},
sequence::{delimited, preceded},
};
use std::convert::TryFrom;
use std::str::FromStr;
use super::*;
use crate::{
email::{
address::{Address, MailboxAddress},
parser::{
generic::{byte_in_range, byte_in_slice},
BytesExt, IResult,
},
},
error::ResultIntoError,
};
bitflags! {
#[derive(Default, Serialize, Deserialize)]
@ -137,7 +141,7 @@ fn test_imap_required_responses() {
let response =
&b"* 1040 FETCH (UID 1064 FLAGS ())\r\nM15 OK Fetch completed (0.001 + 0.299 secs).\r\n"[..];
for l in response.split_rn() {
/*debug!("check line: {}", &l);*/
/* debug!("check line: {}", &l); */
if required_responses.check(l) {
ret.extend_from_slice(l);
}
@ -159,35 +163,59 @@ pub struct ImapLineIterator<'a> {
#[derive(Debug, PartialEq)]
pub enum ResponseCode {
///The human-readable text contains a special alert that MUST be presented to the user in a fashion that calls the user's attention to the message.
///The human-readable text contains a special alert that MUST be presented
/// to the user in a fashion that calls the user's attention to the message.
Alert(String),
///Optionally followed by a parenthesized list of charsets. A SEARCH failed because the given charset is not supported by this implementation. If the optional list of charsets is given, this lists the charsets that are supported by this implementation.
///Optionally followed by a parenthesized list of charsets. A SEARCH
/// failed because the given charset is not supported by this
/// implementation. If the optional list of charsets is given, this lists
/// the charsets that are supported by this implementation.
Badcharset(Option<String>),
/// Followed by a list of capabilities. This can appear in the initial OK or PREAUTH response to transmit an initial capabilities list. This makes it unnecessary for a client to send a separate CAPABILITY command if it recognizes this response.
/// Followed by a list of capabilities. This can appear in the initial OK
/// or PREAUTH response to transmit an initial capabilities list. This
/// makes it unnecessary for a client to send a separate CAPABILITY command
/// if it recognizes this response.
Capability,
/// The human-readable text represents an error in parsing the [RFC-2822] header or [MIME-IMB] headers of a message in the mailbox.
/// The human-readable text represents an error in parsing the [RFC-2822]
/// header or [MIME-IMB] headers of a message in the mailbox.
Parse(String),
/// Followed by a parenthesized list of flags, indicates which of the known flags the client can change permanently. Any flags that are in the FLAGS untagged response, but not the PERMANENTFLAGS list, can not be set permanently. If the client attempts to STORE a flag that is not in the PERMANENTFLAGS list, the server will either ignore the change or store the state change for the remainder of the current session only. The PERMANENTFLAGS list can also include the special flag \*, which indicates that it is possible to create new keywords by attempting to store those flags in the mailbox.
/// Followed by a parenthesized list of flags, indicates which of the known
/// flags the client can change permanently. Any flags that are in the
/// FLAGS untagged response, but not the PERMANENTFLAGS list, can not be set
/// permanently. If the client attempts to STORE a flag that is not in the
/// PERMANENTFLAGS list, the server will either ignore the change or store
/// the state change for the remainder of the current session only. The
/// PERMANENTFLAGS list can also include the special flag \*, which
/// indicates that it is possible to create new keywords by attempting to
/// store those flags in the mailbox.
Permanentflags(String),
/// The mailbox is selected read-only, or its access while selected has changed from read-write to read-only.
/// The mailbox is selected read-only, or its access while selected has
/// changed from read-write to read-only.
ReadOnly,
/// The mailbox is selected read-write, or its access while selected has changed from read-only to read-write.
/// The mailbox is selected read-write, or its access while selected has
/// changed from read-only to read-write.
ReadWrite,
/// An APPEND or COPY attempt is failing because the target mailbox does not exist (as opposed to some other reason). This is a hint to the client that the operation can succeed if the mailbox is first created by the CREATE command.
/// An APPEND or COPY attempt is failing because the target mailbox does not
/// exist (as opposed to some other reason). This is a hint to the client
/// that the operation can succeed if the mailbox is first created by the
/// CREATE command.
Trycreate,
/// Followed by a decimal number, indicates the next unique identifier value. Refer to section 2.3.1.1 for more information.
/// Followed by a decimal number, indicates the next unique identifier
/// value. Refer to section 2.3.1.1 for more information.
Uidnext(UID),
/// Followed by a decimal number, indicates the unique identifier validity value. Refer to section 2.3.1.1 for more information.
/// Followed by a decimal number, indicates the unique identifier validity
/// value. Refer to section 2.3.1.1 for more information.
Uidvalidity(UID),
/// Followed by a decimal number, indicates the number of the first message without the \Seen flag set.
/// Followed by a decimal number, indicates the number of the first message
/// without the \Seen flag set.
Unseen(ImapNum),
}
@ -195,15 +223,23 @@ impl std::fmt::Display for ResponseCode {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
use ResponseCode::*;
match self {
Alert(s)=> write!(fmt, "ALERT: {}", s),
Badcharset(None)=> write!(fmt, "Given charset is not supported by this server."),
Badcharset(Some(s))=> write!(fmt, "Given charset is not supported by this server. Supported ones are: {}", s),
Alert(s) => write!(fmt, "ALERT: {}", s),
Badcharset(None) => write!(fmt, "Given charset is not supported by this server."),
Badcharset(Some(s)) => write!(
fmt,
"Given charset is not supported by this server. Supported ones are: {}",
s
),
Capability => write!(fmt, "Capability response"),
Parse(s) => write!(fmt, "Server error in parsing message headers: {}", s),
Permanentflags(s) => write!(fmt, "Mailbox supports these flags: {}", s),
ReadOnly=> write!(fmt, "This mailbox is selected read-only."),
ReadOnly => write!(fmt, "This mailbox is selected read-only."),
ReadWrite => write!(fmt, "This mailbox is selected with read-write permissions."),
Trycreate => write!(fmt, "Failed to operate on the target mailbox because it doesn't exist. Try creating it first."),
Trycreate => write!(
fmt,
"Failed to operate on the target mailbox because it doesn't exist. Try creating \
it first."
),
Uidnext(uid) => write!(fmt, "Next UID value is {}", uid),
Uidvalidity(uid) => write!(fmt, "Next UIDVALIDITY value is {}", uid),
Unseen(uid) => write!(fmt, "First message without the \\Seen flag is {}", uid),
@ -265,7 +301,8 @@ impl TryFrom<&'_ [u8]> for ImapResponse {
))
})? + 1..]
.trim();
// M12 NO [CANNOT] Invalid mailbox name: Name must not have \'/\' characters (0.000 + 0.098 + 0.097 secs).\r\n
// M12 NO [CANNOT] Invalid mailbox name: Name must not have \'/\' characters
// (0.000 + 0.098 + 0.097 secs).\r\n
if val.ends_with(b" secs).") {
val = &val[..val.rfind(b"(").ok_or_else(|| {
Error::new(format!(
@ -432,8 +469,8 @@ fn test_imap_line_iterator() {
*/
/*
* LIST (\HasNoChildren) "." INBOX.Sent
* LIST (\HasChildren) "." INBOX
* LIST (\HasNoChildren) "." INBOX.Sent
* LIST (\HasChildren) "." INBOX
*/
pub fn list_mailbox_result(input: &[u8]) -> IResult<&[u8], ImapMailbox> {
@ -604,7 +641,8 @@ pub fn fetch_response(input: &[u8]) -> ImapParseResult<FetchResponse<'_>> {
i += (input.len() - i - rest.len()) + 1;
} else {
return debug!(Err(Error::new(format!(
"Unexpected input while parsing UID FETCH response. Could not parse FLAGS: {:.40}.",
"Unexpected input while parsing UID FETCH response. Could not parse FLAGS: \
{:.40}.",
String::from_utf8_lossy(&input[i..])
))));
}
@ -639,7 +677,8 @@ pub fn fetch_response(input: &[u8]) -> ImapParseResult<FetchResponse<'_>> {
i += input.len() - i - rest.len();
} else {
return debug!(Err(Error::new(format!(
"Unexpected input while parsing UID FETCH response. Could not parse RFC822: {:.40}",
"Unexpected input while parsing UID FETCH response. Could not parse RFC822: \
{:.40}",
String::from_utf8_lossy(&input[i..])
))));
}
@ -650,7 +689,8 @@ pub fn fetch_response(input: &[u8]) -> ImapParseResult<FetchResponse<'_>> {
i += input.len() - i - rest.len();
} else {
return debug!(Err(Error::new(format!(
"Unexpected input while parsing UID FETCH response. Could not parse ENVELOPE: {:.40}",
"Unexpected input while parsing UID FETCH response. Could not parse ENVELOPE: \
{:.40}",
String::from_utf8_lossy(&input[i..])
))));
}
@ -672,7 +712,8 @@ pub fn fetch_response(input: &[u8]) -> ImapParseResult<FetchResponse<'_>> {
i += input.len() - i - rest.len();
} else {
return debug!(Err(Error::new(format!(
"Unexpected input while parsing UID FETCH response. Could not parse BODY[HEADER.FIELDS (REFERENCES)]: {:.40}",
"Unexpected input while parsing UID FETCH response. Could not parse \
BODY[HEADER.FIELDS (REFERENCES)]: {:.40}",
String::from_utf8_lossy(&input[i..])
))));
}
@ -688,7 +729,8 @@ pub fn fetch_response(input: &[u8]) -> ImapParseResult<FetchResponse<'_>> {
i += input.len() - i - rest.len();
} else {
return debug!(Err(Error::new(format!(
"Unexpected input while parsing UID FETCH response. Could not parse BODY[HEADER.FIELDS (\"REFERENCES\"): {:.40}",
"Unexpected input while parsing UID FETCH response. Could not parse \
BODY[HEADER.FIELDS (\"REFERENCES\"): {:.40}",
String::from_utf8_lossy(&input[i..])
))));
}
@ -815,9 +857,15 @@ macro_rules! flags_to_imap_list {
/* Input Example:
* ==============
*
* "M0 OK [CAPABILITY IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE IDLE SORT SORT=DISPLAY THREAD=REFERENCES THREAD=REFS THREAD=ORDEREDSUBJECT MULTIAPPEND URL-PARTIAL CATENATE UNSELECT CHILDREN NAMESPACE UIDPLUS LIST-EXTENDED I18NLEVEL=1 CONDSTORE QRESYNC ESEARCH ESORT SEARCHRES WITHIN CONTEXT=SEARCH LIST-STATUS BINARY MOVE SPECIAL-USE] Logged in\r\n"
* "* CAPABILITY IMAP4rev1 UNSELECT IDLE NAMESPACE QUOTA ID XLIST CHILDREN X-GM-EXT-1 XYZZY SASL-IR AUTH=XOAUTH2 AUTH=PLAIN AUTH=PLAIN-CLIENT TOKEN AUTH=OAUTHBEARER AUTH=XOAUTH\r\n"
* "* CAPABILITY IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE IDLE AUTH=PLAIN\r\n"
* "M0 OK [CAPABILITY IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE
* IDLE SORT SORT=DISPLAY THREAD=REFERENCES THREAD=REFS THREAD=ORDEREDSUBJECT
* MULTIAPPEND URL-PARTIAL CATENATE UNSELECT CHILDREN NAMESPACE UIDPLUS
* LIST-EXTENDED I18NLEVEL=1 CONDSTORE QRESYNC ESEARCH ESORT SEARCHRES WITHIN
* CONTEXT=SEARCH LIST-STATUS BINARY MOVE SPECIAL-USE] Logged in\r\n"
* "* CAPABILITY IMAP4rev1 UNSELECT IDLE NAMESPACE QUOTA ID XLIST CHILDREN
* X-GM-EXT-1 XYZZY SASL-IR AUTH=XOAUTH2 AUTH=PLAIN AUTH=PLAIN-CLIENT TOKEN
* AUTH=OAUTHBEARER AUTH=XOAUTH\r\n" "* CAPABILITY IMAP4rev1 LITERAL+
* SASL-IR LOGIN-REFERRALS ID ENABLE IDLE AUTH=PLAIN\r\n"
*/
pub fn capabilities(input: &[u8]) -> IResult<&[u8], Vec<&[u8]>> {
@ -829,7 +877,8 @@ pub fn capabilities(input: &[u8]) -> IResult<&[u8], Vec<&[u8]>> {
Ok((input, ret))
}
/// This enum represents the server's untagged responses detailed in `7. Server Responses` of RFC 3501 INTERNET MESSAGE ACCESS PROTOCOL - VERSION 4rev1
/// This enum represents the server's untagged responses detailed in `7. Server
/// Responses` of RFC 3501 INTERNET MESSAGE ACCESS PROTOCOL - VERSION 4rev1
#[derive(Debug, PartialEq)]
pub enum UntaggedResponse<'s> {
/// ```text
@ -1090,7 +1139,8 @@ pub struct SelectResponse {
/*
*
* * FLAGS (\Answered \Flagged \Deleted \Seen \Draft)
* * OK [PERMANENTFLAGS (\Answered \Flagged \Deleted \Seen \Draft \*)] Flags permitted.
* * OK [PERMANENTFLAGS (\Answered \Flagged \Deleted \Seen \Draft \*)] Flags
* permitted.
* * 45 EXISTS
* * 0 RECENT
* * OK [UNSEEN 16] First unseen.
@ -1283,30 +1333,30 @@ pub fn byte_flags(input: &[u8]) -> IResult<&[u8], (Flag, Vec<String>)> {
}
/*
* The fields of the envelope structure are in the following
* order: date, subject, from, sender, reply-to, to, cc, bcc,
* in-reply-to, and message-id. The date, subject, in-reply-to,
* and message-id fields are strings. The from, sender, reply-to,
* to, cc, and bcc fields are parenthesized lists of address
* structures.
* An address structure is a parenthesized list that describes an
* electronic mail address. The fields of an address structure
* are in the following order: personal name, [SMTP]
* at-domain-list (source route), mailbox name, and host name.
*/
* The fields of the envelope structure are in the following
* order: date, subject, from, sender, reply-to, to, cc, bcc,
* in-reply-to, and message-id. The date, subject, in-reply-to,
* and message-id fields are strings. The from, sender, reply-to,
* to, cc, and bcc fields are parenthesized lists of address
* structures.
* An address structure is a parenthesized list that describes an
* electronic mail address. The fields of an address structure
* are in the following order: personal name, [SMTP]
* at-domain-list (source route), mailbox name, and host name.
*/
/*
* * 12 FETCH (FLAGS (\Seen) INTERNALDATE "17-Jul-1996 02:44:25 -0700"
* RFC822.SIZE 4286 ENVELOPE ("Wed, 17 Jul 1996 02:23:25 -0700 (PDT)"
* "IMAP4rev1 WG mtg summary and minutes"
* (("Terry Gray" NIL "gray" "cac.washington.edu"))
* (("Terry Gray" NIL "gray" "cac.washington.edu"))
* (("Terry Gray" NIL "gray" "cac.washington.edu"))
* ((NIL NIL "imap" "cac.washington.edu"))
* ((NIL NIL "minutes" "CNRI.Reston.VA.US")
* ("John Klensin" NIL "KLENSIN" "MIT.EDU")) NIL NIL
* "<B27397-0100000@cac.washington.edu>")
*/
* * 12 FETCH (FLAGS (\Seen) INTERNALDATE "17-Jul-1996 02:44:25 -0700"
* RFC822.SIZE 4286 ENVELOPE ("Wed, 17 Jul 1996 02:23:25 -0700 (PDT)"
* "IMAP4rev1 WG mtg summary and minutes"
* (("Terry Gray" NIL "gray" "cac.washington.edu"))
* (("Terry Gray" NIL "gray" "cac.washington.edu"))
* (("Terry Gray" NIL "gray" "cac.washington.edu"))
* ((NIL NIL "imap" "cac.washington.edu"))
* ((NIL NIL "minutes" "CNRI.Reston.VA.US")
* ("John Klensin" NIL "KLENSIN" "MIT.EDU")) NIL NIL
* "<B27397-0100000@cac.washington.edu>")
*/
pub fn envelope(input: &[u8]) -> IResult<&[u8], Envelope> {
let (input, _) = tag("(")(input)?;
@ -1466,7 +1516,8 @@ pub fn envelope_address(input: &[u8]) -> IResult<&[u8], Address> {
))
}
// Read a literal ie a byte sequence prefixed with a tag containing its length delimited in {}s
// Read a literal ie a byte sequence prefixed with a tag containing its length
// delimited in {}s
pub fn literal(input: &[u8]) -> IResult<&[u8], &[u8]> {
length_data(delimited(
tag("{"),
@ -1694,7 +1745,8 @@ pub fn string_token(input: &[u8]) -> IResult<&[u8], &[u8]> {
// ASTRING-CHAR = ATOM-CHAR / resp-specials
// atom = 1*ATOM-CHAR
// ATOM-CHAR = <any CHAR except atom-specials>
// atom-specials = "(" / ")" / "{" / SP / CTL / list-wildcards / quoted-specials / resp-specials
// atom-specials = "(" / ")" / "{" / SP / CTL / list-wildcards / quoted-specials
// / resp-specials
fn astring_char(input: &[u8]) -> IResult<&[u8], &[u8]> {
let (rest, chars) = many1(atom_char)(input)?;
Ok((rest, &input[0..chars.len()]))

View File

@ -19,19 +19,22 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use super::{ImapConnection, MailboxSelection, UID};
use crate::backends::imap::protocol_parser::{
generate_envelope_hash, FetchResponse, ImapLineSplit, RequiredResponses, UntaggedResponse,
};
use crate::backends::BackendMailbox;
use crate::backends::{
RefreshEvent,
RefreshEventKind::{self, *},
TagHash,
};
use crate::error::*;
use std::convert::TryInto;
use super::{ImapConnection, MailboxSelection, UID};
use crate::{
backends::{
imap::protocol_parser::{
generate_envelope_hash, FetchResponse, ImapLineSplit, RequiredResponses,
UntaggedResponse,
},
BackendMailbox, RefreshEvent,
RefreshEventKind::{self, *},
TagHash,
},
error::*,
};
impl ImapConnection {
pub async fn process_untagged(&mut self, line: &[u8]) -> Result<bool> {
macro_rules! try_fail {
@ -323,7 +326,11 @@ impl ImapConnection {
accum.push(',');
accum.push_str(to_str!(ms).trim());
}
format!("UID FETCH {} (UID FLAGS ENVELOPE BODY.PEEK[HEADER.FIELDS (REFERENCES)] BODYSTRUCTURE)", accum)
format!(
"UID FETCH {} (UID FLAGS ENVELOPE BODY.PEEK[HEADER.FIELDS \
(REFERENCES)] BODYSTRUCTURE)",
accum
)
};
try_fail!(
mailbox_hash,

View File

@ -18,9 +18,10 @@
* You should have received a copy of the GNU General Public License
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use std::sync::Arc;
use super::*;
use crate::backends::SpecialUsageMailbox;
use std::sync::Arc;
/// Arguments for IMAP watching functions
pub struct ImapWatchKit {
@ -52,8 +53,8 @@ pub async fn poll_with_examine(kit: ImapWatchKit) -> Result<()> {
pub async fn idle(kit: ImapWatchKit) -> Result<()> {
debug!("IDLE");
/* IDLE only watches the connection's selected mailbox. We will IDLE on INBOX and every ~5
* minutes wake up and poll the others */
/* IDLE only watches the connection's selected mailbox. We will IDLE on INBOX
* and every ~5 minutes wake up and poll the others */
let ImapWatchKit {
mut conn,
main_conn,
@ -70,7 +71,10 @@ pub async fn idle(kit: ImapWatchKit) -> Result<()> {
{
Some(mailbox) => mailbox,
None => {
return Err(Error::new("INBOX mailbox not found in local mailbox index. meli may have not parsed the IMAP mailboxes correctly"));
return Err(Error::new(
"INBOX mailbox not found in local mailbox index. meli may have not parsed the \
IMAP mailboxes correctly",
));
}
};
let mailbox_hash = mailbox.hash();
@ -342,7 +346,8 @@ pub async fn examine_updates(
} else if select_response.exists > mailbox.exists.lock().unwrap().len() {
conn.send_command(
format!(
"FETCH {}:* (UID FLAGS ENVELOPE BODY.PEEK[HEADER.FIELDS (REFERENCES)] BODYSTRUCTURE)",
"FETCH {}:* (UID FLAGS ENVELOPE BODY.PEEK[HEADER.FIELDS (REFERENCES)] \
BODYSTRUCTURE)",
std::cmp::max(mailbox.exists.lock().unwrap().len(), 1)
)
.as_bytes(),

View File

@ -19,21 +19,26 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use crate::backends::*;
use crate::conf::AccountSettings;
use crate::connections::timeout;
use crate::email::*;
use crate::error::{Error, Result};
use crate::Collection;
use std::{
collections::{HashMap, HashSet},
convert::TryFrom,
str::FromStr,
sync::{Arc, Mutex, RwLock},
time::{Duration, Instant},
};
use futures::lock::Mutex as FutureMutex;
use isahc::config::RedirectPolicy;
use isahc::{AsyncReadResponseExt, HttpClient};
use isahc::{config::RedirectPolicy, AsyncReadResponseExt, HttpClient};
use serde_json::Value;
use std::collections::{HashMap, HashSet};
use std::convert::TryFrom;
use std::str::FromStr;
use std::sync::{Arc, Mutex, RwLock};
use std::time::{Duration, Instant};
use crate::{
backends::*,
conf::AccountSettings,
connections::timeout,
email::*,
error::{Error, Result},
Collection,
};
#[macro_export]
macro_rules! _impl {
@ -131,7 +136,9 @@ impl JmapServerConf {
^ s.extra.contains_key("server_password"))
{
return Err(Error::new(format!(
"({}) `use_token` use requires either the `server_password_command` set with a command that returns an Bearer token of your account, or `server_password` with the API Bearer token as a string. Consult documentation for guidance.",
"({}) `use_token` use requires either the `server_password_command` set with a \
command that returns an Bearer token of your account, or `server_password` with \
the API Bearer token as a string. Consult documentation for guidance.",
s.name,
)));
}
@ -416,7 +423,13 @@ impl MailBackend for JmapType {
let upload_response: UploadResponse = match serde_json::from_str(&res_text) {
Err(err) => {
let err = Error::new(format!("BUG: Could not deserialize {} server JSON response properly, please report this!\nReply from server: {}", &conn.server_conf.server_url, &res_text)).set_source(Some(Arc::new(err))).set_kind(ErrorKind::Bug);
let err = Error::new(format!(
"BUG: Could not deserialize {} server JSON response properly, please \
report this!\nReply from server: {}",
&conn.server_conf.server_url, &res_text
))
.set_source(Some(Arc::new(err)))
.set_kind(ErrorKind::Bug);
*conn.store.online_status.lock().await = (Instant::now(), Err(err.clone()));
return Err(err);
}
@ -447,7 +460,13 @@ impl MailBackend for JmapType {
let mut v: MethodResponse = match serde_json::from_str(&res_text) {
Err(err) => {
let err = Error::new(format!("BUG: Could not deserialize {} server JSON response properly, please report this!\nReply from server: {}", &conn.server_conf.server_url, &res_text)).set_source(Some(Arc::new(err))).set_kind(ErrorKind::Bug);
let err = Error::new(format!(
"BUG: Could not deserialize {} server JSON response properly, please \
report this!\nReply from server: {}",
&conn.server_conf.server_url, &res_text
))
.set_source(Some(Arc::new(err)))
.set_kind(ErrorKind::Bug);
*conn.store.online_status.lock().await = (Instant::now(), Err(err.clone()));
return Err(err);
}
@ -528,7 +547,13 @@ impl MailBackend for JmapType {
let res_text = res.text().await?;
let mut v: MethodResponse = match serde_json::from_str(&res_text) {
Err(err) => {
let err = Error::new(format!("BUG: Could not deserialize {} server JSON response properly, please report this!\nReply from server: {}", &conn.server_conf.server_url, &res_text)).set_source(Some(Arc::new(err))).set_kind(ErrorKind::Bug);
let err = Error::new(format!(
"BUG: Could not deserialize {} server JSON response properly, please \
report this!\nReply from server: {}",
&conn.server_conf.server_url, &res_text
))
.set_source(Some(Arc::new(err)))
.set_kind(ErrorKind::Bug);
*conn.store.online_status.lock().await = (Instant::now(), Err(err.clone()));
return Err(err);
}
@ -664,7 +689,13 @@ impl MailBackend for JmapType {
let mut v: MethodResponse = match serde_json::from_str(&res_text) {
Err(err) => {
let err = Error::new(format!("BUG: Could not deserialize {} server JSON response properly, please report this!\nReply from server: {}", &conn.server_conf.server_url, &res_text)).set_source(Some(Arc::new(err))).set_kind(ErrorKind::Bug);
let err = Error::new(format!(
"BUG: Could not deserialize {} server JSON response properly, please \
report this!\nReply from server: {}",
&conn.server_conf.server_url, &res_text
))
.set_source(Some(Arc::new(err)))
.set_kind(ErrorKind::Bug);
*conn.store.online_status.lock().await = (Instant::now(), Err(err.clone()));
return Err(err);
}
@ -771,12 +802,22 @@ impl MailBackend for JmapType {
let res_text = res.text().await?;
/*
*{"methodResponses":[["Email/set",{"notUpdated":null,"notDestroyed":null,"oldState":"86","newState":"87","accountId":"u148940c7","updated":{"M045926eed54b11423918f392":{"id":"M045926eed54b11423918f392"}},"created":null,"destroyed":null,"notCreated":null},"m3"]],"sessionState":"cyrus-0;p-5;vfs-0"}
*{"methodResponses":[["Email/set",{"notUpdated":null,"notDestroyed":null,"
* oldState":"86","newState":"87","accountId":"u148940c7","updated":{"
* M045926eed54b11423918f392":{"id":"M045926eed54b11423918f392"}},"created":
* null,"destroyed":null,"notCreated":null},"m3"]],"sessionState":"cyrus-0;
* p-5;vfs-0"}
*/
//debug!("res_text = {}", &res_text);
let mut v: MethodResponse = match serde_json::from_str(&res_text) {
Err(err) => {
let err = Error::new(format!("BUG: Could not deserialize {} server JSON response properly, please report this!\nReply from server: {}", &conn.server_conf.server_url, &res_text)).set_source(Some(Arc::new(err))).set_kind(ErrorKind::Bug);
let err = Error::new(format!(
"BUG: Could not deserialize {} server JSON response properly, please \
report this!\nReply from server: {}",
&conn.server_conf.server_url, &res_text
))
.set_source(Some(Arc::new(err)))
.set_kind(ErrorKind::Bug);
*conn.store.online_status.lock().await = (Instant::now(), Err(err.clone()));
return Err(err);
}

View File

@ -19,10 +19,12 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use super::*;
use isahc::config::Configurable;
use std::sync::MutexGuard;
use isahc::config::Configurable;
use super::*;
#[derive(Debug)]
pub struct JmapConnection {
pub session: Arc<Mutex<JmapSession>>,
@ -73,11 +75,22 @@ impl JmapConnection {
let mut jmap_session_resource_url = self.server_conf.server_url.to_string();
jmap_session_resource_url.push_str("/.well-known/jmap");
let mut req = self.client.get_async(&jmap_session_resource_url).await.map_err(|err| {
let err = Error::new(format!("Could not connect to JMAP server endpoint for {}. Is your server url setting correct? (i.e. \"jmap.mailserver.org\") (Note: only session resource discovery via /.well-known/jmap is supported. DNS SRV records are not suppported.)\nError connecting to server: {}", &self.server_conf.server_url, &err)).set_source(Some(Arc::new(err)));
let mut req = self
.client
.get_async(&jmap_session_resource_url)
.await
.map_err(|err| {
let err = Error::new(format!(
"Could not connect to JMAP server endpoint for {}. Is your server url setting \
correct? (i.e. \"jmap.mailserver.org\") (Note: only session resource \
discovery via /.well-known/jmap is supported. DNS SRV records are not \
suppported.)\nError connecting to server: {}",
&self.server_conf.server_url, &err
))
.set_source(Some(Arc::new(err)));
//*self.store.online_status.lock().await = (Instant::now(), Err(err.clone()));
err
})?;
})?;
if !req.status().is_success() {
let kind: crate::error::NetworkErrorKind = req.status().into();
@ -95,7 +108,14 @@ impl JmapConnection {
let session: JmapSession = match serde_json::from_str(&res_text) {
Err(err) => {
let err = Error::new(format!("Could not connect to JMAP server endpoint for {}. Is your server url setting correct? (i.e. \"jmap.mailserver.org\") (Note: only session resource discovery via /.well-known/jmap is supported. DNS SRV records are not suppported.)\nReply from server: {}", &self.server_conf.server_url, &res_text)).set_source(Some(Arc::new(err)));
let err = Error::new(format!(
"Could not connect to JMAP server endpoint for {}. Is your server url setting \
correct? (i.e. \"jmap.mailserver.org\") (Note: only session resource \
discovery via /.well-known/jmap is supported. DNS SRV records are not \
suppported.)\nReply from server: {}",
&self.server_conf.server_url, &res_text
))
.set_source(Some(Arc::new(err)));
*self.store.online_status.lock().await = (Instant::now(), Err(err.clone()));
return Err(err);
}
@ -105,7 +125,17 @@ impl JmapConnection {
.capabilities
.contains_key("urn:ietf:params:jmap:core")
{
let err = Error::new(format!("Server {} did not return JMAP Core capability (urn:ietf:params:jmap:core). Returned capabilities were: {}", &self.server_conf.server_url, session.capabilities.keys().map(String::as_str).collect::<Vec<&str>>().join(", ")));
let err = Error::new(format!(
"Server {} did not return JMAP Core capability (urn:ietf:params:jmap:core). \
Returned capabilities were: {}",
&self.server_conf.server_url,
session
.capabilities
.keys()
.map(String::as_str)
.collect::<Vec<&str>>()
.join(", ")
));
*self.store.online_status.lock().await = (Instant::now(), Err(err.clone()));
return Err(err);
}
@ -113,7 +143,17 @@ impl JmapConnection {
.capabilities
.contains_key("urn:ietf:params:jmap:mail")
{
let err = Error::new(format!("Server {} does not support JMAP Mail capability (urn:ietf:params:jmap:mail). Returned capabilities were: {}", &self.server_conf.server_url, session.capabilities.keys().map(String::as_str).collect::<Vec<&str>>().join(", ")));
let err = Error::new(format!(
"Server {} does not support JMAP Mail capability (urn:ietf:params:jmap:mail). \
Returned capabilities were: {}",
&self.server_conf.server_url,
session
.capabilities
.keys()
.map(String::as_str)
.collect::<Vec<&str>>()
.join(", ")
));
*self.store.online_status.lock().await = (Instant::now(), Err(err.clone()));
return Err(err);
}
@ -207,7 +247,13 @@ impl JmapConnection {
debug!(&res_text);
let mut v: MethodResponse = match serde_json::from_str(&res_text) {
Err(err) => {
let err = Error::new(format!("BUG: Could not deserialize {} server JSON response properly, please report this!\nReply from server: {}", &self.server_conf.server_url, &res_text)).set_source(Some(Arc::new(err))).set_kind(ErrorKind::Bug);
let err = Error::new(format!(
"BUG: Could not deserialize {} server JSON response properly, please \
report this!\nReply from server: {}",
&self.server_conf.server_url, &res_text
))
.set_source(Some(Arc::new(err)))
.set_kind(ErrorKind::Bug);
*self.store.online_status.lock().await = (Instant::now(), Err(err.clone()));
return Err(err);
}

View File

@ -19,9 +19,10 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use std::sync::{Arc, Mutex, RwLock};
use super::*;
use crate::backends::{LazyCountSet, MailboxPermissions, SpecialUsageMailbox};
use std::sync::{Arc, Mutex, RwLock};
#[derive(Debug, Clone)]
pub struct JmapMailbox {

View File

@ -19,15 +19,18 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use super::*;
use crate::backends::jmap::rfc8620::bool_false;
use crate::email::address::{Address, MailboxAddress};
use core::marker::PhantomData;
use serde::de::{Deserialize, Deserializer};
use serde_json::value::RawValue;
use serde_json::Value;
use std::collections::HashMap;
use serde::de::{Deserialize, Deserializer};
use serde_json::{value::RawValue, Value};
use super::*;
use crate::{
backends::jmap::rfc8620::bool_false,
email::address::{Address, MailboxAddress},
};
mod import;
pub use import::*;
@ -83,14 +86,13 @@ impl Id<EmailObject> {
// first character changed from "\" in IMAP to "$" in JMAP and have
// particular semantic meaning:
//
// * "$draft": The Email is a draft the user is composing.
// * "$draft": The Email is a draft the user is composing.
//
// * "$seen": The Email has been read.
// * "$seen": The Email has been read.
//
// * "$flagged": The Email has been flagged for urgent/special
// attention.
// * "$flagged": The Email has been flagged for urgent/special attention.
//
// * "$answered": The Email has been replied to.
// * "$answered": The Email has been replied to.
//
// The IMAP "\Recent" keyword is not exposed via JMAP. The IMAP
// "\Deleted" keyword is also not present: IMAP uses a delete+expunge
@ -115,19 +117,19 @@ impl Id<EmailObject> {
// keywords in common use. New keywords may be established here in
// the future. In particular, note:
//
// * "$forwarded": The Email has been forwarded.
// * "$forwarded": The Email has been forwarded.
//
// * "$phishing": The Email is highly likely to be phishing.
// Clients SHOULD warn users to take care when viewing this Email
// and disable links and attachments.
// * "$phishing": The Email is highly likely to be phishing. Clients SHOULD
// warn users to take care when viewing this Email and disable links and
// attachments.
//
// * "$junk": The Email is definitely spam. Clients SHOULD set this
// flag when users report spam to help train automated spam-
// detection systems.
// * "$junk": The Email is definitely spam. Clients SHOULD set this flag
// when users report spam to help train automated spam- detection
// systems.
//
// * "$notjunk": The Email is definitely not spam. Clients SHOULD
// set this flag when users indicate an Email is legitimate, to
// help train automated spam-detection systems.
// * "$notjunk": The Email is definitely not spam. Clients SHOULD set this
// flag when users indicate an Email is legitimate, to help train
// automated spam-detection systems.
//
// o size: "UnsignedInt" (immutable; server-set)
//
@ -586,8 +588,10 @@ impl From<crate::search::Query> for Filter<EmailFilterCondition, EmailObject> {
fn from(val: crate::search::Query) -> Self {
let mut ret = Filter::Condition(EmailFilterCondition::new().into());
fn rec(q: &crate::search::Query, f: &mut Filter<EmailFilterCondition, EmailObject>) {
use crate::datetime::{timestamp_to_string, RFC3339_FMT};
use crate::search::Query::*;
use crate::{
datetime::{timestamp_to_string, RFC3339_FMT},
search::Query::*,
};
match q {
Subject(t) => {
*f = Filter::Condition(EmailFilterCondition::new().subject(t.clone()).into());
@ -849,8 +853,16 @@ pub struct EmailQueryChangesResponse {
impl std::convert::TryFrom<&RawValue> for EmailQueryChangesResponse {
type Error = crate::error::Error;
fn try_from(t: &RawValue) -> Result<EmailQueryChangesResponse> {
let res: (String, EmailQueryChangesResponse, String) =
serde_json::from_str(t.get()).map_err(|err| crate::error::Error::new(format!("BUG: Could not deserialize server JSON response properly, please report this!\nReply from server: {}", &t)).set_source(Some(Arc::new(err))).set_kind(ErrorKind::Bug))?;
let res: (String, EmailQueryChangesResponse, String) = serde_json::from_str(t.get())
.map_err(|err| {
crate::error::Error::new(format!(
"BUG: Could not deserialize server JSON response properly, please report \
this!\nReply from server: {}",
&t
))
.set_source(Some(Arc::new(err)))
.set_kind(ErrorKind::Bug)
})?;
assert_eq!(&res.0, "Email/queryChanges");
Ok(res.1)
}

View File

@ -19,9 +19,10 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use super::*;
use serde_json::value::RawValue;
use super::*;
/// #`import`
///
/// Objects of type `Foo` are imported via a call to `Foo/import`.
@ -31,7 +32,6 @@ use serde_json::value::RawValue;
/// - `account_id`: "Id"
///
/// The id of the account to use.
///
#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct ImportCall {
@ -81,10 +81,9 @@ impl ImportCall {
}
_impl!(
/// - accountId: "Id"
/// - accountId: "Id"
///
/// The id of the account to use.
///
account_id: Id<Account>
);
_impl!(if_in_state: Option<State<EmailObject>>);
@ -123,9 +122,10 @@ pub enum ImportError {
AlreadyExists {
description: Option<String>,
/// An "existingId" property of type "Id" MUST be included on
///the SetError object with the id of the existing Email. If duplicates
///are allowed, the newly created Email object MUST have a separate id
///and independent mutable properties to the existing object.
///the SetError object with the id of the existing Email. If
/// duplicates are allowed, the newly created Email object MUST
/// have a separate id and independent mutable properties to the
/// existing object.
existing_id: Id<EmailObject>,
},
///If the "blobId", "mailboxIds", or "keywords" properties are invalid
@ -146,7 +146,8 @@ pub enum ImportError {
///different to the "blobId" on the EmailImport object. Alternatively,
///the server MAY reject the import with an "invalidEmail" SetError.
InvalidEmail { description: Option<String> },
///An "ifInState" argument was supplied, and it does not match the current state.
///An "ifInState" argument was supplied, and it does not match the current
/// state.
StateMismatch,
}
@ -185,7 +186,15 @@ impl std::convert::TryFrom<&RawValue> for ImportResponse {
type Error = crate::error::Error;
fn try_from(t: &RawValue) -> Result<ImportResponse> {
let res: (String, ImportResponse, String) =
serde_json::from_str(t.get()).map_err(|err| crate::error::Error::new(format!("BUG: Could not deserialize server JSON response properly, please report this!\nReply from server: {}", &t)).set_source(Some(Arc::new(err))).set_kind(ErrorKind::Bug))?;
serde_json::from_str(t.get()).map_err(|err| {
crate::error::Error::new(format!(
"BUG: Could not deserialize server JSON response properly, please report \
this!\nReply from server: {}",
&t
))
.set_source(Some(Arc::new(err)))
.set_kind(ErrorKind::Bug)
})?;
assert_eq!(&res.0, &ImportCall::NAME);
Ok(res.1)
}

View File

@ -19,9 +19,10 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use super::*;
use std::sync::Arc;
use super::*;
/// `BackendOp` implementor for Imap
#[derive(Debug, Clone)]
pub struct JmapOp {

View File

@ -19,11 +19,12 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use super::mailbox::JmapMailbox;
use super::*;
use std::convert::{TryFrom, TryInto};
use serde::Serialize;
use serde_json::{json, Value};
use std::convert::{TryFrom, TryInto};
use super::{mailbox::JmapMailbox, *};
pub type UtcDate = String;
@ -97,7 +98,13 @@ pub async fn get_mailboxes(conn: &JmapConnection) -> Result<HashMap<MailboxHash,
let res_text = res.text().await?;
let mut v: MethodResponse = match serde_json::from_str(&res_text) {
Err(err) => {
let err = Error::new(format!("BUG: Could not deserialize {} server JSON response properly, please report this!\nReply from server: {}", &conn.server_conf.server_url, &res_text)).set_source(Some(Arc::new(err))).set_kind(ErrorKind::Bug);
let err = Error::new(format!(
"BUG: Could not deserialize {} server JSON response properly, please report \
this!\nReply from server: {}",
&conn.server_conf.server_url, &res_text
))
.set_source(Some(Arc::new(err)))
.set_kind(ErrorKind::Bug);
*conn.store.online_status.lock().await = (Instant::now(), Err(err.clone()));
return Err(err);
}
@ -108,8 +115,9 @@ pub async fn get_mailboxes(conn: &JmapConnection) -> Result<HashMap<MailboxHash,
let GetResponse::<MailboxObject> {
list, account_id, ..
} = m;
// Is account set as `personal`? (`isPersonal` property). Then, even if `isSubscribed` is false
// on a mailbox, it should be regarded as subscribed.
// Is account set as `personal`? (`isPersonal` property). Then, even if
// `isSubscribed` is false on a mailbox, it should be regarded as
// subscribed.
let is_personal: bool = {
let session = conn.session_guard();
session
@ -204,7 +212,13 @@ pub async fn get_message_list(
let res_text = res.text().await?;
let mut v: MethodResponse = match serde_json::from_str(&res_text) {
Err(err) => {
let err = Error::new(format!("BUG: Could not deserialize {} server JSON response properly, please report this!\nReply from server: {}", &conn.server_conf.server_url, &res_text)).set_source(Some(Arc::new(err))).set_kind(ErrorKind::Bug);
let err = Error::new(format!(
"BUG: Could not deserialize {} server JSON response properly, please report \
this!\nReply from server: {}",
&conn.server_conf.server_url, &res_text
))
.set_source(Some(Arc::new(err)))
.set_kind(ErrorKind::Bug);
*conn.store.online_status.lock().await = (Instant::now(), Err(err.clone()));
return Err(err);
}
@ -284,7 +298,13 @@ pub async fn fetch(
let mut v: MethodResponse = match serde_json::from_str(&res_text) {
Err(err) => {
let err = Error::new(format!("BUG: Could not deserialize {} server JSON response properly, please report this!\nReply from server: {}", &conn.server_conf.server_url, &res_text)).set_source(Some(Arc::new(err))).set_kind(ErrorKind::Bug);
let err = Error::new(format!(
"BUG: Could not deserialize {} server JSON response properly, please report \
this!\nReply from server: {}",
&conn.server_conf.server_url, &res_text
))
.set_source(Some(Arc::new(err)))
.set_kind(ErrorKind::Bug);
*conn.store.online_status.lock().await = (Instant::now(), Err(err.clone()));
return Err(err);
}

View File

@ -19,23 +19,30 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use crate::email::parser::BytesExt;
use core::marker::PhantomData;
use serde::de::DeserializeOwned;
use serde::ser::{Serialize, SerializeStruct, Serializer};
use std::{
hash::{Hash, Hasher},
sync::Arc,
};
use serde::{
de::DeserializeOwned,
ser::{Serialize, SerializeStruct, Serializer},
};
use serde_json::{value::RawValue, Value};
use std::hash::{Hash, Hasher};
use std::sync::Arc;
use crate::email::parser::BytesExt;
mod filters;
pub use filters::*;
mod comparator;
pub use comparator::*;
mod argument;
use std::collections::HashMap;
pub use argument::*;
use super::protocol::Method;
use std::collections::HashMap;
pub trait Object {
const NAME: &'static str;
}
@ -275,7 +282,6 @@ impl Object for BlobObject {
/// - `account_id`: "Id"
///
/// The id of the account to use.
///
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Get<OBJ: Object>
@ -305,31 +311,30 @@ where
}
}
_impl!(
/// - accountId: "Id"
/// - accountId: "Id"
///
/// The id of the account to use.
///
account_id: Id<Account>
);
_impl!(
/// - ids: `Option<JmapArgument<Vec<String>>>`
///
/// The ids of the Foo objects to return. If `None`, then *all* records
/// of the data type are returned, if this is supported for that data
/// type and the number of records does not exceed the
/// "max_objects_in_get" limit.
/// - ids: `Option<JmapArgument<Vec<String>>>`
///
/// The ids of the Foo objects to return. If `None`, then *all*
/// records of the data type are returned, if this is
/// supported for that data type and the number of records
/// does not exceed the "max_objects_in_get" limit.
ids: Option<JmapArgument<Vec<Id<OBJ>>>>
);
_impl!(
/// - properties: Option<Vec<String>>
/// - properties: Option<Vec<String>>
///
/// If supplied, only the properties listed in the array are returned
/// for each `Foo` object. If `None`, all properties of the object are
/// returned. The `id` property of the object is *always* returned,
/// even if not explicitly requested. If an invalid property is
/// requested, the call WILL be rejected with an "invalid_arguments"
/// error.
/// If supplied, only the properties listed in the array are
/// returned for each `Foo` object. If `None`, all
/// properties of the object are returned. The `id`
/// property of the object is *always* returned, even if
/// not explicitly requested. If an invalid property is
/// requested, the call WILL be rejected with an
/// "invalid_arguments" error.
properties: Option<Vec<String>>
);
}
@ -414,7 +419,15 @@ impl<OBJ: Object + DeserializeOwned> std::convert::TryFrom<&RawValue> for GetRes
type Error = crate::error::Error;
fn try_from(t: &RawValue) -> Result<GetResponse<OBJ>, crate::error::Error> {
let res: (String, GetResponse<OBJ>, String) =
serde_json::from_str(t.get()).map_err(|err| crate::error::Error::new(format!("BUG: Could not deserialize server JSON response properly, please report this!\nReply from server: {}", &t)).set_source(Some(Arc::new(err))).set_kind(crate::error::ErrorKind::Bug))?;
serde_json::from_str(t.get()).map_err(|err| {
crate::error::Error::new(format!(
"BUG: Could not deserialize server JSON response properly, please report \
this!\nReply from server: {}",
&t
))
.set_source(Some(Arc::new(err)))
.set_kind(crate::error::ErrorKind::Bug)
})?;
assert_eq!(&res.0, &format!("{}/get", OBJ::NAME));
Ok(res.1)
}
@ -519,7 +532,15 @@ impl<OBJ: Object + DeserializeOwned> std::convert::TryFrom<&RawValue> for QueryR
type Error = crate::error::Error;
fn try_from(t: &RawValue) -> Result<QueryResponse<OBJ>, crate::error::Error> {
let res: (String, QueryResponse<OBJ>, String) =
serde_json::from_str(t.get()).map_err(|err| crate::error::Error::new(format!("BUG: Could not deserialize server JSON response properly, please report this!\nReply from server: {}", &t)).set_source(Some(Arc::new(err))).set_kind(crate::error::ErrorKind::Bug))?;
serde_json::from_str(t.get()).map_err(|err| {
crate::error::Error::new(format!(
"BUG: Could not deserialize server JSON response properly, please report \
this!\nReply from server: {}",
&t
))
.set_source(Some(Arc::new(err)))
.set_kind(crate::error::ErrorKind::Bug)
})?;
assert_eq!(&res.0, &format!("{}/query", OBJ::NAME));
Ok(res.1)
}
@ -543,8 +564,8 @@ impl<M: Method<OBJ>, OBJ: Object> ResultField<M, OBJ> {
}
}
// error[E0723]: trait bounds other than `Sized` on const fn parameters are unstable
// --> melib/src/backends/jmap/rfc8620.rs:626:6
// error[E0723]: trait bounds other than `Sized` on const fn parameters are
// unstable --> melib/src/backends/jmap/rfc8620.rs:626:6
// |
// 626 | impl<M: Method<OBJ>, OBJ: Object> ResultField<M, OBJ> {
// | ^
@ -562,8 +583,9 @@ impl<M: Method<OBJ>, OBJ: Object> ResultField<M, OBJ> {
/// #`changes`
///
/// The "Foo/changes" method allows a client to efficiently update the state of its Foo cache
/// to match the new state on the server. It takes the following arguments:
/// The "Foo/changes" method allows a client to efficiently update the state
/// of its Foo cache to match the new state on the server. It takes the
/// following arguments:
///
/// - accountId: "Id" The id of the account to use.
/// - sinceState: "String"
@ -579,7 +601,6 @@ impl<M: Method<OBJ>, OBJ: Object> ResultField<M, OBJ> {
/// to return. If supplied by the client, the value MUST be a
/// positive integer greater than 0. If a value outside of this range
/// is given, the server MUST re
///
#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
/* ch-ch-ch-ch-ch-Changes */
@ -608,10 +629,9 @@ where
}
}
_impl!(
/// - accountId: "Id"
/// - accountId: "Id"
///
/// The id of the account to use.
///
account_id: Id<Account>
);
_impl!(
@ -620,8 +640,6 @@ where
/// returned as the "state" argument in the "Foo/get" response. The
/// server will return the changes that have occurred since this
/// state.
///
///
since_state: State<OBJ>
);
_impl!(
@ -630,8 +648,8 @@ where
/// MAY choose to return fewer than this value but MUST NOT return
/// more. If not given by the client, the server may choose how many
/// to return. If supplied by the client, the value MUST be a
/// positive integer greater than 0. If a value outside of this range
/// is given, the server MUST re
/// positive integer greater than 0. If a value outside of this
/// range is given, the server MUST re
max_changes: Option<u64>
);
}
@ -654,7 +672,15 @@ impl<OBJ: Object + DeserializeOwned> std::convert::TryFrom<&RawValue> for Change
type Error = crate::error::Error;
fn try_from(t: &RawValue) -> Result<ChangesResponse<OBJ>, crate::error::Error> {
let res: (String, ChangesResponse<OBJ>, String) =
serde_json::from_str(t.get()).map_err(|err| crate::error::Error::new(format!("BUG: Could not deserialize server JSON response properly, please report this!\nReply from server: {}", &t)).set_source(Some(Arc::new(err))).set_kind(crate::error::ErrorKind::Bug))?;
serde_json::from_str(t.get()).map_err(|err| {
crate::error::Error::new(format!(
"BUG: Could not deserialize server JSON response properly, please report \
this!\nReply from server: {}",
&t
))
.set_source(Some(Arc::new(err)))
.set_kind(crate::error::ErrorKind::Bug)
})?;
assert_eq!(&res.0, &format!("{}/changes", OBJ::NAME));
Ok(res.1)
}
@ -707,7 +733,6 @@ where
///
/// The client MUST omit any properties that may only be set by the
/// server (for example, the "id" property on most object types).
///
pub create: Option<HashMap<Id<OBJ>, OBJ>>,
///o update: "Id[PatchObject]|null"
///
@ -722,26 +747,26 @@ where
/// All paths MUST also conform to the following restrictions; if
/// there is any violation, the update MUST be rejected with an
/// "invalidPatch" error:
/// * The pointer MUST NOT reference inside an array (i.e., you MUST
/// NOT insert/delete from an array; the array MUST be replaced in
/// its entirety instead).
/// * The pointer MUST NOT reference inside an array (i.e., you MUST NOT
/// insert/delete from an array; the array MUST be replaced in its
/// entirety instead).
///
/// * All parts prior to the last (i.e., the value after the final
/// slash) MUST already exist on the object being patched.
/// * All parts prior to the last (i.e., the value after the final slash)
/// MUST already exist on the object being patched.
///
/// * There MUST NOT be two patches in the PatchObject where the
/// pointer of one is the prefix of the pointer of the other, e.g.,
/// "alerts/1/offset" and "alerts".
/// * There MUST NOT be two patches in the PatchObject where the pointer
/// of one is the prefix of the pointer of the other, e.g.,
/// "alerts/1/offset" and "alerts".
///
/// The value associated with each pointer determines how to apply
/// that patch:
///
/// * If null, set to the default value if specified for this
/// property; otherwise, remove the property from the patched
/// object. If the key is not present in the parent, this a no-op.
/// * If null, set to the default value if specified for this property;
/// otherwise, remove the property from the patched object. If the key
/// is not present in the parent, this a no-op.
///
/// * Anything else: The value to set for this property (this may be
/// a replacement or addition to the object being patched).
/// * Anything else: The value to set for this property (this may be a
/// replacement or addition to the object being patched).
///
/// Any server-set properties MAY be included in the patch if their
/// value is identical to the current server value (before applying
@ -853,7 +878,15 @@ impl<OBJ: Object + DeserializeOwned> std::convert::TryFrom<&RawValue> for SetRes
type Error = crate::error::Error;
fn try_from(t: &RawValue) -> Result<SetResponse<OBJ>, crate::error::Error> {
let res: (String, SetResponse<OBJ>, String) =
serde_json::from_str(t.get()).map_err(|err| crate::error::Error::new(format!("BUG: Could not deserialize server JSON response properly, please report this!\nReply from server: {}", &t)).set_source(Some(Arc::new(err))).set_kind(crate::error::ErrorKind::Bug))?;
serde_json::from_str(t.get()).map_err(|err| {
crate::error::Error::new(format!(
"BUG: Could not deserialize server JSON response properly, please report \
this!\nReply from server: {}",
&t
))
.set_source(Some(Arc::new(err)))
.set_kind(crate::error::ErrorKind::Bug)
})?;
assert_eq!(&res.0, &format!("{}/set", OBJ::NAME));
Ok(res.1)
}
@ -863,31 +896,41 @@ impl<OBJ: Object + DeserializeOwned> std::convert::TryFrom<&RawValue> for SetRes
#[serde(rename_all = "camelCase")]
#[serde(tag = "type", content = "description")]
pub enum SetError {
///(create; update; destroy). The create/update/destroy would violate an ACL or other permissions policy.
///(create; update; destroy). The create/update/destroy would violate an
/// ACL or other permissions policy.
Forbidden(Option<String>),
///(create; update). The create would exceed a server- defined limit on the number or total size of objects of this type.
///(create; update). The create would exceed a server- defined limit on
/// the number or total size of objects of this type.
OverQuota(Option<String>),
///(create; update). The create/update would result in an object that exceeds a server-defined limit for the maximum size of a single object of this type.
///(create; update). The create/update would result in an object that
/// exceeds a server-defined limit for the maximum size of a single object
/// of this type.
TooLarge(Option<String>),
///(create). Too many objects of this type have been created recently, and a server-defined rate limit has been reached. It may work if tried again later.
///(create). Too many objects of this type have been created recently, and
/// a server-defined rate limit has been reached. It may work if tried
/// again later.
RateLimit(Option<String>),
///(update; destroy). The id given to update/destroy cannot be found.
NotFound(Option<String>),
///(update). The PatchObject given to update the record was not a valid patch (see the patch description).
///(update). The PatchObject given to update the record was not a valid
/// patch (see the patch description).
InvalidPatch(Option<String>),
///(update). The client requested that an object be both updated and destroyed in the same /set request, and the server has decided to therefore ignore the update.
///(update). The client requested that an object be both updated and
/// destroyed in the same /set request, and the server has decided to
/// therefore ignore the update.
WillDestroy(Option<String>),
///(create; update). The record given is invalid in some way.
InvalidProperties {
description: Option<String>,
properties: Vec<String>,
},
///(create; destroy). This is a singleton type, so you cannot create another one or destroy the existing one.
///(create; destroy). This is a singleton type, so you cannot create
/// another one or destroy the existing one.
Singleton(Option<String>),
RequestTooLarge(Option<String>),
StateMismatch(Option<String>),
@ -1001,8 +1044,9 @@ pub struct UploadResponse {
pub account_id: Id<Account>,
///o blobId: "Id"
///
///The id representing the binary data uploaded. The data for this id is immutable.
///The id *only* refers to the binary data, not any metadata.
///The id representing the binary data uploaded. The data for this id is
/// immutable. The id *only* refers to the binary data, not any
/// metadata.
pub blob_id: Id<BlobObject>,
///o type: "String"
///
@ -1098,11 +1142,15 @@ where
pub struct QueryChangesResponse<OBJ: Object> {
/// The id of the account used for the call.
pub account_id: Id<Account>,
/// This is the "sinceQueryState" argument echoed back; that is, the state from which the server is returning changes.
/// This is the "sinceQueryState" argument echoed back; that is, the state
/// from which the server is returning changes.
pub old_query_state: String,
///This is the state the query will be in after applying the set of changes to the old state.
///This is the state the query will be in after applying the set of changes
/// to the old state.
pub new_query_state: String,
/// The total number of Foos in the results (given the "filter"). This argument MUST be omitted if the "calculateTotal" request argument is not true.
/// The total number of Foos in the results (given the "filter"). This
/// argument MUST be omitted if the "calculateTotal" request argument is not
/// true.
#[serde(default)]
pub total: Option<usize>,
///The "id" for every Foo that was in the query results in the old
@ -1139,9 +1187,9 @@ pub struct QueryChangesResponse<OBJ: Object> {
///An *AddedItem* object has the following properties:
///* id: "Id"
/// * id: "Id"
///* index: "UnsignedInt"
/// * index: "UnsignedInt"
///The result of this is that if the client has a cached sparse array of
///Foo ids corresponding to the results in the old state, then:

View File

@ -19,9 +19,10 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use crate::backends::jmap::protocol::Method;
use crate::backends::jmap::rfc8620::Object;
use crate::backends::jmap::rfc8620::ResultField;
use crate::backends::jmap::{
protocol::Method,
rfc8620::{Object, ResultField},
};
#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]

View File

@ -24,19 +24,24 @@ mod backend;
pub use self::backend::*;
mod stream;
use std::{
collections::hash_map::DefaultHasher,
fs,
hash::{Hash, Hasher},
io::{BufReader, Read},
path::{Path, PathBuf},
sync::{Arc, Mutex},
};
use futures::stream::Stream;
pub use stream::*;
use crate::backends::*;
use crate::email::Flag;
use crate::error::{Error, Result};
use crate::shellexpand::ShellExpandTrait;
use futures::stream::Stream;
use std::collections::hash_map::DefaultHasher;
use std::fs;
use std::hash::{Hash, Hasher};
use std::io::{BufReader, Read};
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use crate::{
backends::*,
email::Flag,
error::{Error, Result},
shellexpand::ShellExpandTrait,
};
/// `BackendOp` implementor for Maildir
#[derive(Debug)]
@ -96,7 +101,7 @@ impl<'a> BackendOp for MaildirOp {
let file = std::fs::OpenOptions::new()
.read(true)
.write(false)
.open(&self.path()?)?;
.open(self.path()?)?;
let mut buf_reader = BufReader::new(file);
let mut contents = Vec::new();
buf_reader.read_to_end(&mut contents)?;
@ -141,8 +146,8 @@ impl MaildirMailbox {
let mut h = DefaultHasher::new();
pathbuf.hash(&mut h);
/* Check if mailbox path (Eg `INBOX/Lists/luddites`) is included in the subscribed
* mailboxes in user configuration */
/* Check if mailbox path (Eg `INBOX/Lists/luddites`) is included in the
* subscribed mailboxes in user configuration */
let fname = pathbuf
.strip_prefix(
PathBuf::from(&settings.root_mailbox)
@ -279,7 +284,11 @@ impl MaildirPathTrait for Path {
'S' => flag |= Flag::SEEN,
'T' => flag |= Flag::TRASHED,
_ => {
debug!("DEBUG: in MaildirPathTrait::flags(), encountered unknown flag marker {:?}, path is {}", f, path);
debug!(
"DEBUG: in MaildirPathTrait::flags(), encountered unknown flag marker \
{:?}, path is {}",
f, path
);
}
}
}

View File

@ -21,32 +21,36 @@
//! # Maildir Backend
//!
//! This module implements a maildir backend according to the maildir specification.
//! <https://cr.yp.to/proto/maildir.html>
//! This module implements a maildir backend according to the maildir
//! specification. <https://cr.yp.to/proto/maildir.html>
use super::{MaildirMailbox, MaildirOp, MaildirPathTrait};
use crate::backends::{RefreshEventKind::*, *};
use crate::conf::AccountSettings;
use crate::email::{Envelope, EnvelopeHash, Flag};
use crate::error::{Error, ErrorKind, Result};
use crate::shellexpand::ShellExpandTrait;
use crate::Collection;
use futures::prelude::Stream;
extern crate notify;
use self::notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
use std::time::Duration;
use super::{MaildirMailbox, MaildirOp, MaildirPathTrait};
use crate::{
backends::{RefreshEventKind::*, *},
conf::AccountSettings,
email::{Envelope, EnvelopeHash, Flag},
error::{Error, ErrorKind, Result},
shellexpand::ShellExpandTrait,
Collection,
};
use std::collections::{hash_map::DefaultHasher, HashMap, HashSet};
use std::ffi::OsStr;
use std::fs;
use std::hash::{Hash, Hasher};
use std::io::{self, Read, Write};
use std::ops::{Deref, DerefMut};
use std::os::unix::fs::PermissionsExt;
use std::path::{Component, Path, PathBuf};
use std::sync::mpsc::channel;
use std::sync::{Arc, Mutex};
extern crate notify;
use std::{
collections::{hash_map::DefaultHasher, HashMap, HashSet},
ffi::OsStr,
fs,
hash::{Hash, Hasher},
io::{self, Read, Write},
ops::{Deref, DerefMut},
os::unix::fs::PermissionsExt,
path::{Component, Path, PathBuf},
sync::{mpsc::channel, Arc, Mutex},
time::Duration,
};
use self::notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
#[derive(Clone, Debug, PartialEq)]
pub(super) enum PathMod {
@ -669,7 +673,10 @@ impl MailBackend for MaildirType {
e.modified = Some(PathMod::Hash(new_hash));
e.removed = false;
});
debug!("contains_old_key, key was marked as removed (by external source)");
debug!(
"contains_old_key, key was marked as removed (by external \
source)"
);
} else {
debug!("not contains_new_key");
}
@ -893,7 +900,7 @@ impl MailBackend for MaildirType {
Some(PathMod::Path(new_name.clone()));
debug!("renaming {:?} to {:?}", path, new_name);
fs::rename(&path, &new_name)?;
fs::rename(path, &new_name)?;
debug!("success in rename");
}
Ok(())
@ -996,12 +1003,16 @@ impl MailBackend for MaildirType {
let mut path = self.path.clone();
path.push(&new_path);
if !path.starts_with(&self.path) {
return Err(Error::new(format!("Path given (`{}`) is absolute. Please provide a path relative to the account's root mailbox.", &new_path)));
return Err(Error::new(format!(
"Path given (`{}`) is absolute. Please provide a path relative to the account's \
root mailbox.",
&new_path
)));
}
std::fs::create_dir(&path)?;
/* create_dir does not create intermediate directories (like `mkdir -p`), so the parent must be a valid
* mailbox at this point. */
/* create_dir does not create intermediate directories (like `mkdir -p`), so
* the parent must be a valid mailbox at this point. */
let parent = path.parent().and_then(|p| {
self.mailboxes
@ -1143,8 +1154,9 @@ impl MaildirType {
children.push(f.hash);
mailboxes.insert(f.hash, f);
} else {
/* If directory is invalid (i.e. has no {cur,new,tmp} subfolders),
* accept it ONLY if it contains subdirs of any depth that are
/* If directory is invalid (i.e. has no {cur,new,tmp}
* subfolders), accept it ONLY if
* it contains subdirs of any depth that are
* valid maildir paths
*/
let subdirs = recurse_mailboxes(mailboxes, settings, &path)?;
@ -1379,7 +1391,7 @@ fn add_path_to_index(
map.len()
);
}
let mut reader = io::BufReader::new(fs::File::open(&path)?);
let mut reader = io::BufReader::new(fs::File::open(path)?);
buf.clear();
reader.read_to_end(buf)?;
let mut env = Envelope::from_bytes(buf.as_slice(), Some(path.flags()))?;

View File

@ -19,17 +19,22 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use core::{future::Future, pin::Pin};
use std::{
io::{self, Read},
os::unix::fs::PermissionsExt,
path::PathBuf,
result,
sync::{Arc, Mutex},
};
use futures::{
stream::{FuturesUnordered, StreamExt},
task::{Context, Poll},
};
use super::*;
use crate::backends::maildir::backend::move_to_cur;
use core::future::Future;
use core::pin::Pin;
use futures::stream::{FuturesUnordered, StreamExt};
use futures::task::{Context, Poll};
use std::io::{self, Read};
use std::os::unix::fs::PermissionsExt;
use std::path::PathBuf;
use std::result;
use std::sync::{Arc, Mutex};
pub struct MaildirStream {
payloads: Pin<
@ -66,7 +71,7 @@ impl MaildirStream {
files
.chunks(chunk_size)
.map(|chunk| {
let cache_dir = xdg::BaseDirectories::with_profile("meli", &name).unwrap();
let cache_dir = xdg::BaseDirectories::with_profile("meli", name).unwrap();
Box::pin(Self::chunk(
SmallVec::from(chunk),
cache_dir,

View File

@ -31,36 +31,44 @@
//!
//! `mbox` describes a family of incompatible legacy formats.
//!
//! "All of the 'mbox' formats store all of the messages in the mailbox in a single file. Delivery appends new messages to the end of the file." [^0]
//! "All of the 'mbox' formats store all of the messages in the mailbox in a
//! single file. Delivery appends new messages to the end of the file." [^0]
//!
//! "Each message is preceded by a From_ line and followed by a blank line. A From_ line is a line that begins with the five characters 'F', 'r', 'o', 'm', and ' '." [^0]
//! "Each message is preceded by a From_ line and followed by a blank line. A
//! From_ line is a line that begins with the five characters 'F', 'r', 'o',
//! 'm', and ' '." [^0]
//!
//! ## `From ` / postmark line
//!
//! "An mbox is a text file containing an arbitrary number of e-mail messages. Each message
//! consists of a postmark, followed by an e-mail message formatted according to RFC822, RFC2822.
//! The file format is line-oriented. Lines are separated by line feed characters (ASCII 10).
//! "An mbox is a text file containing an arbitrary number of e-mail messages.
//! Each message consists of a postmark, followed by an e-mail message formatted
//! according to RFC822, RFC2822. The file format is line-oriented. Lines are
//! separated by line feed characters (ASCII 10).
//!
//! "A postmark line consists of the four characters 'From', followed by a space character,
//! followed by the message's envelope sender address, followed by whitespace, and followed by a
//! time stamp. This line is often called From_ line.
//! "A postmark line consists of the four characters 'From', followed by a space
//! character, followed by the message's envelope sender address, followed by
//! whitespace, and followed by a time stamp. This line is often called From_
//! line.
//!
//! "The sender address is expected to be addr-spec as defined in RFC2822 3.4.1. The date is expected
//! to be date-time as output by asctime(3). For compatibility reasons with legacy software,
//! two-digit years greater than or equal to 70 should be interpreted as the years 1970+, while
//! two-digit years less than 70 should be interpreted as the years 2000-2069. Software reading
//! files in this format should also be prepared to accept non-numeric timezone information such as
//! 'CET DST' for Central European Time, daylight saving time.
//! "The sender address is expected to be addr-spec as defined in RFC2822 3.4.1.
//! The date is expected to be date-time as output by asctime(3). For
//! compatibility reasons with legacy software, two-digit years greater than or
//! equal to 70 should be interpreted as the years 1970+, while two-digit years
//! less than 70 should be interpreted as the years 2000-2069. Software reading
//! files in this format should also be prepared to accept non-numeric timezone
//! information such as 'CET DST' for Central European Time, daylight saving
//! time.
//!
//! "Example:
//!
//!```text
//!From example@example.com Fri Jun 23 02:56:55 2000
//!```
//! From example@example.com Fri Jun 23 02:56:55 2000
//! ```
//!
//! "In order to avoid misinterpretation of lines in message bodies which begin with the four
//! characters 'From', followed by a space character, the mail delivery agent must quote
//! any occurrence of 'From ' at the start of a body line." [^2]
//! "In order to avoid misinterpretation of lines in message bodies which begin
//! with the four characters 'From', followed by a space character, the mail
//! delivery agent must quote any occurrence of 'From ' at the start of a body
//! line." [^2]
//!
//! ## Metadata
//!
@ -77,7 +85,8 @@
//! # use std::collections::HashMap;
//! # use std::sync::{Arc, Mutex};
//! let file_contents = vec![]; // Replace with actual mbox file contents
//! let index: Arc<Mutex<HashMap<EnvelopeHash, (Offset, Length)>>> = Arc::new(Mutex::new(HashMap::default()));
//! let index: Arc<Mutex<HashMap<EnvelopeHash, (Offset, Length)>>> =
//! Arc::new(Mutex::new(HashMap::default()));
//! let mut message_iter = MessageIterator {
//! index: index.clone(),
//! input: &file_contents.as_slice(),
@ -100,9 +109,9 @@
//! format.append(
//! &mut file,
//! mbox_1,
//! None, // Envelope From
//! None, // Envelope From
//! Some(melib::datetime::now()), // Delivered date
//! Default::default(), // Flags and tags
//! Default::default(), // Flags and tags
//! MboxMetadata::None,
//! true,
//! false,
@ -121,29 +130,37 @@
//! # Ok::<(), melib::Error>(())
//! ```
use crate::backends::*;
use crate::collection::Collection;
use crate::conf::AccountSettings;
use crate::email::parser::BytesExt;
use crate::email::*;
use crate::error::{Error, ErrorKind, Result};
use crate::get_path_hash;
use crate::shellexpand::ShellExpandTrait;
use nom::bytes::complete::tag;
use nom::character::complete::digit1;
use nom::combinator::map_res;
use nom::{self, error::Error as NomError, error::ErrorKind as NomErrorKind, IResult};
use nom::{
self,
bytes::complete::tag,
character::complete::digit1,
combinator::map_res,
error::{Error as NomError, ErrorKind as NomErrorKind},
IResult,
};
use crate::{
backends::*,
collection::Collection,
conf::AccountSettings,
email::{parser::BytesExt, *},
error::{Error, ErrorKind, Result},
get_path_hash,
shellexpand::ShellExpandTrait,
};
extern crate notify;
use std::{
collections::hash_map::HashMap,
fs::File,
io::{BufReader, Read},
os::unix::io::AsRawFd,
path::{Path, PathBuf},
str::FromStr,
sync::{mpsc::channel, Arc, Mutex, RwLock},
};
use self::notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
use std::collections::hash_map::HashMap;
use std::fs::File;
use std::io::{BufReader, Read};
use std::os::unix::io::AsRawFd;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::sync::mpsc::channel;
use std::sync::{Arc, Mutex, RwLock};
pub mod write;
@ -163,7 +180,8 @@ fn get_rw_lock_blocking(f: &File, path: &Path) -> Result<()> {
l_start: 0,
l_len: 0, /* "Specifying 0 for l_len has the special meaning: lock all bytes starting at the location
specified by l_whence and l_start through to the end of file, no matter how large the file grows." */
l_pid: 0, /* "By contrast with traditional record locks, the l_pid field of that structure must be set to zero when using the commands described below." */
l_pid: 0, /* "By contrast with traditional record locks, the l_pid field of that
* structure must be set to zero when using the commands described below." */
#[cfg(target_os = "freebsd")]
l_sysid: 0,
};
@ -368,9 +386,12 @@ 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”
/// 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: Messages allocated UID
/// - Status: R (Seen) and O (non-Recent) flags
/// - X-Status: A (Answered), F (Flagged), T (Draft) and D (Deleted) flags
@ -380,8 +401,8 @@ pub enum MboxMetadata {
None,
}
/// Choose between "mboxo", "mboxrd", "mboxcl", "mboxcl2". For new mailboxes, prefer "mboxcl2"
/// which does not alter the mail body.
/// Choose between "mboxo", "mboxrd", "mboxcl", "mboxcl2". For new mailboxes,
/// prefer "mboxcl2" which does not alter the mail body.
#[derive(Debug, Clone, Copy)]
pub enum MboxFormat {
MboxO,
@ -1406,7 +1427,8 @@ impl MboxType {
);
} else {
return Err(Error::new(format!(
"mbox mailbox configuration entry \"{}\" should have a \"path\" value set pointing to an mbox file.",
"mbox mailbox configuration entry \"{}\" should have a \"path\" value set \
pointing to an mbox file.",
k
)));
}

View File

@ -21,14 +21,14 @@
//! # NNTP backend / client
//!
//! Implements an NNTP client as specified by [RFC 3977: Network News Transfer Protocol
//! (NNTP)](https://datatracker.ietf.org/doc/html/rfc3977). Also implements [RFC 6048: Network News
//! Implements an NNTP client as specified by [RFC 3977: Network News Transfer
//! Protocol (NNTP)](https://datatracker.ietf.org/doc/html/rfc3977). Also implements [RFC 6048: Network News
//! Transfer Protocol (NNTP) Additions to LIST
//! Command](https://datatracker.ietf.org/doc/html/rfc6048).
use crate::get_conf_val;
use crate::get_path_hash;
use smallvec::SmallVec;
use crate::{get_conf_val, get_path_hash};
#[macro_use]
mod protocol_parser;
pub use protocol_parser::*;
@ -37,21 +37,26 @@ pub use mailbox::*;
mod operations;
pub use operations::*;
mod connection;
pub use connection::*;
use std::{
collections::{hash_map::DefaultHasher, BTreeSet, HashMap, HashSet},
hash::Hasher,
pin::Pin,
str::FromStr,
sync::{Arc, Mutex},
time::{Duration, Instant},
};
use crate::conf::AccountSettings;
use crate::connections::timeout;
use crate::email::*;
use crate::error::{Error, Result, ResultIntoError};
use crate::{backends::*, Collection};
use futures::lock::Mutex as FutureMutex;
use futures::stream::Stream;
use std::collections::{hash_map::DefaultHasher, BTreeSet, HashMap, HashSet};
use std::hash::Hasher;
use std::pin::Pin;
use std::str::FromStr;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
pub use connection::*;
use futures::{lock::Mutex as FutureMutex, stream::Stream};
use crate::{
backends::*,
conf::AccountSettings,
connections::timeout,
email::*,
error::{Error, Result, ResultIntoError},
Collection,
};
pub type UID = usize;
macro_rules! get_conf_val {
@ -253,9 +258,21 @@ impl MailBackend for NntpType {
let uid_store = self.uid_store.clone();
let connection = self.connection.clone();
Ok(Box::pin(async move {
/* To get updates, either issue NEWNEWS if it's supported by the server, and fallback
* to OVER otherwise */
let mbox: NntpMailbox = uid_store.mailboxes.lock().await.get(&mailbox_hash).map(std::clone::Clone::clone).ok_or_else(|| Error::new(format!("Mailbox with hash {} not found in NNTP connection, this could possibly be a bug or it was deleted.", mailbox_hash)))?;
/* To get updates, either issue NEWNEWS if it's supported by the server, and
* fallback to OVER otherwise */
let mbox: NntpMailbox = uid_store
.mailboxes
.lock()
.await
.get(&mailbox_hash)
.map(std::clone::Clone::clone)
.ok_or_else(|| {
Error::new(format!(
"Mailbox with hash {} not found in NNTP connection, this could possibly \
be a bug or it was deleted.",
mailbox_hash
))
})?;
let latest_article: Option<crate::UnixTimestamp> = *mbox.latest_article.lock().unwrap();
let (over_msgid_support, newnews_support): (bool, bool) = {
let caps = uid_store.capabilities.lock().unwrap();
@ -374,15 +391,15 @@ impl MailBackend for NntpType {
}
fn operation(&self, env_hash: EnvelopeHash) -> Result<Box<dyn BackendOp>> {
let (uid, mailbox_hash) = if let Some(v) =
self.uid_store.hash_index.lock().unwrap().get(&env_hash)
{
*v
} else {
return Err(Error::new(
"Message not found in local cache, it might have been deleted before you requested it."
let (uid, mailbox_hash) =
if let Some(v) = self.uid_store.hash_index.lock().unwrap().get(&env_hash) {
*v
} else {
return Err(Error::new(
"Message not found in local cache, it might have been deleted before you \
requested it.",
));
};
};
Ok(Box::new(NntpOp::new(
uid,
mailbox_hash,
@ -671,34 +688,35 @@ impl NntpType {
pub fn validate_config(s: &mut AccountSettings) -> Result<()> {
let mut keys: HashSet<&'static str> = Default::default();
macro_rules! get_conf_val {
($s:ident[$var:literal]) => {{
keys.insert($var);
$s.extra.remove($var).ok_or_else(|| {
Error::new(format!(
"Configuration error ({}): NNTP connection requires the field `{}` set",
$s.name.as_str(),
$var
))
})
}};
($s:ident[$var:literal], $default:expr) => {{
keys.insert($var);
$s.extra
.remove($var)
.map(|v| {
<_>::from_str(&v).map_err(|e| {
($s:ident[$var:literal]) => {{
keys.insert($var);
$s.extra.remove($var).ok_or_else(|| {
Error::new(format!(
"Configuration error ({}) NNTP: Invalid value for field `{}`: {}\n{}",
"Configuration error ({}): NNTP connection requires the field `{}` set",
$s.name.as_str(),
$var,
v,
e
$var
))
})
})
.unwrap_or_else(|| Ok($default))
}};
}
}};
($s:ident[$var:literal], $default:expr) => {{
keys.insert($var);
$s.extra
.remove($var)
.map(|v| {
<_>::from_str(&v).map_err(|e| {
Error::new(format!(
"Configuration error ({}) NNTP: Invalid value for field `{}`: \
{}\n{}",
$s.name.as_str(),
$var,
v,
e
))
})
})
.unwrap_or_else(|| Ok($default))
}};
}
get_conf_val!(s["require_auth"], false)?;
get_conf_val!(s["server_hostname"])?;
get_conf_val!(s["server_username"], String::new())?;
@ -706,7 +724,8 @@ impl NntpType {
get_conf_val!(s["server_password"], String::new())?;
} else if s.extra.contains_key("server_password") {
return Err(Error::new(format!(
"Configuration error ({}): both server_password and server_password_command are set, cannot choose",
"Configuration error ({}): both server_password and server_password_command are \
set, cannot choose",
s.name.as_str(),
)));
}
@ -716,7 +735,8 @@ impl NntpType {
let use_starttls = get_conf_val!(s["use_starttls"], server_port != 563)?;
if !use_tls && use_starttls {
return Err(Error::new(format!(
"Configuration error ({}): incompatible use_tls and use_starttls values: use_tls = false, use_starttls = true",
"Configuration error ({}): incompatible use_tls and use_starttls values: use_tls \
= false, use_starttls = true",
s.name.as_str(),
)));
}
@ -725,7 +745,8 @@ impl NntpType {
#[cfg(not(feature = "deflate_compression"))]
if s.extra.contains_key("use_deflate") {
return Err(Error::new(format!(
"Configuration error ({}): setting `use_deflate` is set but this version of meli isn't compiled with DEFLATE support.",
"Configuration error ({}): setting `use_deflate` is set but this version of meli \
isn't compiled with DEFLATE support.",
s.name.as_str(),
)));
}
@ -738,8 +759,10 @@ impl NntpType {
let diff = extra_keys.difference(&keys).collect::<Vec<&&str>>();
if !diff.is_empty() {
return Err(Error::new(format!(
"Configuration error ({}) NNTP: the following flags are set but are not recognized: {:?}.",
s.name.as_str(), diff
"Configuration error ({}) NNTP: the following flags are set but are not \
recognized: {:?}.",
s.name.as_str(),
diff
)));
}
Ok(())

View File

@ -19,19 +19,18 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use crate::backends::{BackendMailbox, MailboxHash};
use crate::connections::{lookup_ipv4, Connection};
use crate::email::parser::BytesExt;
use crate::error::*;
use crate::{
backends::{BackendMailbox, MailboxHash},
connections::{lookup_ipv4, Connection},
email::parser::BytesExt,
error::*,
};
extern crate native_tls;
use std::{collections::HashSet, future::Future, pin::Pin, sync::Arc, time::Instant};
use futures::io::{AsyncReadExt, AsyncWriteExt};
use native_tls::TlsConnector;
pub use smol::Async as AsyncWrapper;
use std::collections::HashSet;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::time::Instant;
use super::{Capabilities, NntpServerConf, UIDStore};

View File

@ -19,13 +19,16 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use crate::backends::{
BackendMailbox, LazyCountSet, Mailbox, MailboxHash, MailboxPermissions, SpecialUsageMailbox,
};
use crate::error::*;
use crate::UnixTimestamp;
use std::sync::{Arc, Mutex};
use crate::{
backends::{
BackendMailbox, LazyCountSet, Mailbox, MailboxHash, MailboxPermissions, SpecialUsageMailbox,
},
error::*,
UnixTimestamp,
};
#[derive(Debug, Default, Clone)]
pub struct NntpMailbox {
pub(super) hash: MailboxHash,

View File

@ -19,13 +19,11 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use super::*;
use crate::backends::*;
use crate::email::*;
use crate::error::Error;
use std::sync::Arc;
use super::*;
use crate::{backends::*, email::*, error::Error};
/// `BackendOp` implementor for Nntp
#[derive(Debug, Clone)]
pub struct NntpOp {

View File

@ -19,13 +19,15 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use super::*;
use crate::email::parser::IResult;
use std::str::FromStr;
use nom::{
bytes::complete::{is_not, tag},
combinator::opt,
};
use std::str::FromStr;
use super::*;
use crate::email::parser::IResult;
pub struct NntpLineIterator<'a> {
slice: &'a str,

View File

@ -19,18 +19,25 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use crate::conf::AccountSettings;
use crate::email::{Envelope, EnvelopeHash, Flag};
use crate::error::{Error, Result};
use crate::shellexpand::ShellExpandTrait;
use crate::{backends::*, Collection};
use std::{
collections::{hash_map::HashMap, BTreeMap},
ffi::{CStr, CString, OsStr},
io::Read,
os::unix::ffi::OsStrExt,
path::{Path, PathBuf},
sync::{Arc, Mutex, RwLock},
};
use smallvec::SmallVec;
use std::collections::{hash_map::HashMap, BTreeMap};
use std::ffi::{CStr, CString, OsStr};
use std::io::Read;
use std::os::unix::ffi::OsStrExt;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex, RwLock};
use crate::{
backends::*,
conf::AccountSettings,
email::{Envelope, EnvelopeHash, Flag},
error::{Error, Result},
shellexpand::ShellExpandTrait,
Collection,
};
macro_rules! call {
($lib:expr, $func:ty) => {{
@ -316,9 +323,14 @@ impl NotmuchDb {
Ok(l) => l,
Err(err) => {
if custom_dlpath {
return Err(Error::new(format!("Notmuch `library_file_path` setting value `{}` for account {} does not exist or is a directory or not a valid library file.",dlpath, s.name()))
.set_kind(ErrorKind::Configuration)
.set_source(Some(Arc::new(err))));
return Err(Error::new(format!(
"Notmuch `library_file_path` setting value `{}` for account {} does \
not exist or is a directory or not a valid library file.",
dlpath,
s.name()
))
.set_kind(ErrorKind::Configuration)
.set_source(Some(Arc::new(err))));
} else {
return Err(Error::new("Could not load libnotmuch!")
.set_details(super::NOTMUCH_ERROR_DETAILS)
@ -347,10 +359,12 @@ impl NotmuchDb {
path.push(".notmuch");
if !path.exists() || !path.is_dir() {
return Err(Error::new(format!(
"Notmuch `root_mailbox` {} for account {} does not contain a `.notmuch` subdirectory.",
"Notmuch `root_mailbox` {} for account {} does not contain a `.notmuch` \
subdirectory.",
s.root_mailbox.as_str(),
s.name()
)).set_kind(ErrorKind::Configuration));
))
.set_kind(ErrorKind::Configuration));
}
path.pop();
@ -378,7 +392,8 @@ impl NotmuchDb {
);
} else {
return Err(Error::new(format!(
"notmuch mailbox configuration entry `{}` for account {} should have a `query` value set.",
"notmuch mailbox configuration entry `{}` for account {} should have a \
`query` value set.",
k,
s.name(),
))
@ -399,7 +414,8 @@ impl NotmuchDb {
mailboxes.entry(hash).or_default().parent = Some(parent_hash);
} else {
return Err(Error::new(format!(
"Mailbox configuration for `{}` defines its parent mailbox as `{}` but no mailbox exists with this exact name.",
"Mailbox configuration for `{}` defines its parent mailbox as `{}` but no \
mailbox exists with this exact name.",
mailboxes[&hash].name(),
parent
))
@ -445,10 +461,12 @@ impl NotmuchDb {
path.push(".notmuch");
if !path.exists() || !path.is_dir() {
return Err(Error::new(format!(
"Notmuch `root_mailbox` {} for account {} does not contain a `.notmuch` subdirectory.",
"Notmuch `root_mailbox` {} for account {} does not contain a `.notmuch` \
subdirectory.",
s.root_mailbox.as_str(),
s.name()
)).set_kind(ErrorKind::Configuration));
))
.set_kind(ErrorKind::Configuration));
}
path.pop();
@ -456,19 +474,21 @@ impl NotmuchDb {
if let Some(lib_path) = s.extra.remove("library_file_path") {
if !Path::new(&lib_path).exists() || Path::new(&lib_path).is_dir() {
return Err(Error::new(format!(
"Notmuch `library_file_path` setting value `{}` for account {} does not exist or is a directory.",
&lib_path,
s.name()
)).set_kind(ErrorKind::Configuration));
"Notmuch `library_file_path` setting value `{}` for account {} does not exist \
or is a directory.",
&lib_path,
s.name()
))
.set_kind(ErrorKind::Configuration));
}
}
let mut parents: Vec<(String, String)> = Vec::with_capacity(s.mailboxes.len());
for (k, f) in s.mailboxes.iter_mut() {
if f.extra.remove("query").is_none() {
return Err(Error::new(format!(
"notmuch mailbox configuration entry `{}` for account {} should have a `query` value set.",
k,
account_name,
"notmuch mailbox configuration entry `{}` for account {} should have a \
`query` value set.",
k, account_name,
))
.set_kind(ErrorKind::Configuration));
}
@ -480,9 +500,9 @@ impl NotmuchDb {
for (mbox, parent) in parents.iter() {
if !s.mailboxes.contains_key(parent) {
return Err(Error::new(format!(
"Mailbox configuration for `{}` defines its parent mailbox as `{}` but no mailbox exists with this exact name.",
mbox,
parent
"Mailbox configuration for `{}` defines its parent mailbox as `{}` but no \
mailbox exists with this exact name.",
mbox, parent
))
.set_kind(ErrorKind::Configuration));
}

View File

@ -244,7 +244,6 @@ pub type notmuch_database_open_verbose = unsafe extern "C" fn(
) -> notmuch_status_t;
/// Retrieve last status string for given database.
///
pub type notmuch_database_status_string =
unsafe extern "C" fn(notmuch: *const notmuch_database_t) -> *const ::std::os::raw::c_char;
@ -509,7 +508,6 @@ extern "C" {
/// @deprecated Deprecated as of libnotmuch 5.1 (notmuch 0.26). Please
/// use notmuch_database_index_file instead.
/// ```
///
pub fn notmuch_database_add_message(
database: *mut notmuch_database_t,
filename: *const ::std::os::raw::c_char,
@ -751,7 +749,7 @@ pub type notmuch_query_add_tag_exclude = unsafe extern "C" fn(
/// }
///
/// notmuch_query_destroy (query);
///```
/// ```
///
/// Note: If you are finished with a thread before its containing
/// query, you can call notmuch_thread_destroy to clean up some memory
@ -779,7 +777,6 @@ pub type notmuch_query_search_threads = unsafe extern "C" fn(
/// @deprecated Deprecated as of libnotmuch 5 (notmuch 0.25). Please
/// ```
/// use notmuch_query_search_threads instead.
///
pub type notmuch_query_search_threads_st = unsafe extern "C" fn(
query: *mut notmuch_query_t,
out: *mut *mut notmuch_threads_t,
@ -809,7 +806,7 @@ pub type notmuch_query_search_threads_st = unsafe extern "C" fn(
/// }
///
/// notmuch_query_destroy (query);
///```
/// ```
///
/// Note: If you are finished with a message before its containing
/// query, you can call notmuch_message_destroy to clean up some memory
@ -839,7 +836,6 @@ pub type notmuch_query_search_messages = unsafe extern "C" fn(
/// @deprecated Deprecated as of libnotmuch 5 (notmuch 0.25). Please use
/// ```
/// notmuch_query_search_messages instead.
///
pub type notmuch_query_search_messages_st = unsafe extern "C" fn(
query: *mut notmuch_query_t,
out: *mut *mut notmuch_messages_t,
@ -1091,7 +1087,7 @@ pub type notmuch_thread_get_newest_date =
/// }
///
/// notmuch_thread_destroy (thread);
///```
/// ```
///
/// Note that there's no explicit destructor needed for the
/// notmuch_tags_t object. (For consistency, we do provide a
@ -1250,7 +1246,8 @@ pub type notmuch_message_get_filename =
pub type notmuch_message_get_filenames =
unsafe extern "C" fn(message: *mut notmuch_message_t) -> *mut notmuch_filenames_t;
/// Re-index the e-mail corresponding to 'message' using the supplied index options
/// Re-index the e-mail corresponding to 'message' using the supplied index
/// options
///
/// Returns the status of the re-index operation. (see the return
/// codes documented in notmuch_database_index_file)
@ -1333,7 +1330,7 @@ pub type notmuch_message_get_header = unsafe extern "C" fn(
/// }
///
/// notmuch_message_destroy (message);
///```
/// ```
///
/// Note that there's no explicit destructor needed for the
/// notmuch_tags_t object. (For consistency, we do provide a
@ -1423,7 +1420,6 @@ pub type notmuch_message_maildir_flags_to_tags =
/// return TRUE if any filename of 'message' has maildir flag 'flag',
/// FALSE otherwise.
///
pub type notmuch_message_has_maildir_flag = unsafe extern "C" fn(
message: *mut notmuch_message_t,
flag: ::std::os::raw::c_char,
@ -1673,7 +1669,7 @@ extern "C" {
/// }
///
/// notmuch_message_properties_destroy (list);
///```
/// ```
///
/// Note that there's no explicit destructor needed for the
/// notmuch_message_properties_t object. (For consistency, we do
@ -1689,7 +1685,8 @@ extern "C" {
exact: notmuch_bool_t,
) -> *mut notmuch_message_properties_t;
}
/// Return the number of properties named "key" belonging to the specific message.
/// Return the number of properties named "key" belonging to the specific
/// message.
///
/// ```text
/// @param[in] message The message to examine
@ -1970,7 +1967,8 @@ pub type notmuch_database_get_config_list = unsafe extern "C" fn(
out: *mut *mut notmuch_config_list_t,
) -> notmuch_status_t;
/// Is 'config_list' iterator valid (i.e. _key, _value, _move_to_next can be called).
/// Is 'config_list' iterator valid (i.e. _key, _value, _move_to_next can be
/// called).
///
/// ```text
/// @since libnotmuch 4.4 (notmuch 0.23)

View File

@ -3,23 +3,23 @@
*
* Copyright (c) 2021 Ilya Medvedev
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
/* Code from <https://github.com/iam-medvedev/rust-utf7-imap> */
@ -111,7 +111,7 @@ fn encode_modified_utf7(text: &str) -> String {
/// <https://datatracker.ietf.org/doc/html/rfc3501#section-5.1.3>
pub fn decode_utf7_imap(text: &str) -> String {
let pattern = Regex::new(r"&([^-]*)-").unwrap();
pattern.replace_all(&text, expand).to_string()
pattern.replace_all(text, expand).to_string()
}
fn expand(cap: &Captures) -> String {

View File

@ -19,13 +19,16 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use std::{
collections::{BTreeMap, HashMap, HashSet},
ops::{Deref, DerefMut},
sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard},
};
use smallvec::SmallVec;
use super::*;
use crate::backends::{MailboxHash, TagHash};
use smallvec::SmallVec;
use std::ops::{Deref, DerefMut};
use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
use std::collections::{BTreeMap, HashMap, HashSet};
pub type EnvelopeRef<'g> = RwRef<'g, EnvelopeHash, Envelope>;
pub type EnvelopeRefMut<'g> = RwRefMut<'g, EnvelopeHash, Envelope>;

View File

@ -19,13 +19,18 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
//! Basic mail account configuration to use with [`backends`](./backends/index.html)
use crate::backends::SpecialUsageMailbox;
use crate::error::{Error, Result};
pub use crate::{SortField, SortOrder};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
//! Basic mail account configuration to use with
//! [`backends`](./backends/index.html)
use std::collections::HashMap;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::{
backends::SpecialUsageMailbox,
error::{Error, Result},
};
pub use crate::{SortField, SortOrder};
#[derive(Debug, Serialize, Default, Clone)]
pub struct AccountSettings {
pub name: String,
@ -87,7 +92,7 @@ impl AccountSettings {
pub fn server_password(&self) -> Result<String> {
if let Some(cmd) = self.extra.get("server_password_command") {
let output = std::process::Command::new("sh")
.args(&["-c", cmd])
.args(["-c", cmd])
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
@ -107,7 +112,10 @@ impl AccountSettings {
} else if let Some(pass) = self.extra.get("server_password") {
Ok(pass.to_owned())
} else {
Err(Error::new(format!("Configuration error: connection requires either server_password or server_password_command")))
Err(Error::new(format!(
"Configuration error: connection requires either server_password or \
server_password_command"
)))
}
}
}

View File

@ -20,6 +20,8 @@
*/
//! Connections layers (TCP/fd/TLS/Deflate) to use with remote backends.
use std::{os::unix::io::AsRawFd, time::Duration};
#[cfg(feature = "deflate_compression")]
use flate2::{read::DeflateDecoder, write::DeflateEncoder, Compression};
#[cfg(any(target_os = "openbsd", target_os = "netbsd", target_os = "haiku"))]
@ -35,8 +37,6 @@ use libc::TCP_KEEPALIVE as KEEPALIVE_OPTION;
)))]
use libc::TCP_KEEPIDLE as KEEPALIVE_OPTION;
use libc::{self, c_int, c_void};
use std::os::unix::io::AsRawFd;
use std::time::Duration;
#[derive(Debug)]
pub enum Connection {

View File

@ -37,11 +37,14 @@
//! let s = timestamp_to_string(timestamp, Some("%Y-%m-%d"), true);
//! assert_eq!(s, "2020-01-08");
//! ```
use std::{
borrow::Cow,
convert::TryInto,
ffi::{CStr, CString},
os::raw::c_int,
};
use crate::error::{Result, ResultIntoError};
use std::borrow::Cow;
use std::convert::TryInto;
use std::ffi::{CStr, CString};
use std::os::raw::c_int;
pub type UnixTimestamp = u64;
pub const RFC3339_FMT_WITH_TIME: &str = "%Y-%m-%dT%H:%M:%S\0";
@ -122,7 +125,8 @@ impl Drop for Locale {
}
}
// How to unit test this? Test machine is not guaranteed to have non-english locales.
// How to unit test this? Test machine is not guaranteed to have non-english
// locales.
impl Locale {
#[cfg(not(target_os = "netbsd"))]
fn new(

View File

@ -24,8 +24,9 @@
*
* # Parsing bytes into an `Envelope`
*
* An [`Envelope`](Envelope) represents the information you can get from an email's headers and body
* structure. Addresses in `To`, `From` fields etc are parsed into [`Address`](crate::email::Address) types.
* An [`Envelope`](Envelope) represents the information you can get from an
* email's headers and body structure. Addresses in `To`, `From` fields etc
* are parsed into [`Address`](crate::email::Address) types.
*
* ```
* use melib::{Attachment, Envelope};
@ -75,7 +76,10 @@
*
* let envelope = Envelope::from_bytes(raw_mail.as_bytes(), None).expect("Could not parse mail");
* assert_eq!(envelope.subject().as_ref(), "gratuitously encoded subject");
* assert_eq!(envelope.message_id_display().as_ref(), "<h2g7f.z0gy2pgaen5m@example.com>");
* assert_eq!(
* envelope.message_id_display().as_ref(),
* "<h2g7f.z0gy2pgaen5m@example.com>"
* );
*
* let body = envelope.body_bytes(raw_mail.as_bytes());
* assert_eq!(body.content_type().to_string().as_str(), "multipart/mixed");
@ -85,7 +89,10 @@
*
* let subattachments: Vec<Attachment> = body.attachments();
* assert_eq!(subattachments.len(), 3);
* assert_eq!(subattachments[2].content_type().name().unwrap(), "test_image.gif");
* assert_eq!(
* subattachments[2].content_type().name().unwrap(),
* "test_image.gif"
* );
* ```
*/
@ -99,22 +106,22 @@ pub mod mailto;
pub mod parser;
pub mod pgp;
use std::{borrow::Cow, convert::TryInto, ops::Deref};
pub use address::{Address, MessageID, References, StrBuild, StrBuilder};
pub use attachments::{Attachment, AttachmentBuilder};
pub use compose::{attachment_from_file, Draft};
pub use headers::*;
pub use mailto::*;
use crate::datetime::UnixTimestamp;
use crate::error::{Error, Result};
use crate::parser::BytesExt;
use crate::thread::ThreadNodeHash;
use crate::TagHash;
use smallvec::SmallVec;
use std::borrow::Cow;
use std::convert::TryInto;
use std::ops::Deref;
use crate::{
datetime::UnixTimestamp,
error::{Error, Result},
parser::BytesExt,
thread::ThreadNodeHash,
TagHash,
};
bitflags! {
#[derive(Default, Serialize, Deserialize)]
@ -159,9 +166,10 @@ impl Flag {
flag_impl!(fn is_flagged, Flag::FLAGGED);
}
///`Mail` holds both the envelope info of an email in its `envelope` field and the raw bytes that
///describe the email in `bytes`. Its body as an `melib::email::Attachment` can be parsed on demand
///with the `melib::email::Mail::body` method.
///`Mail` holds both the envelope info of an email in its `envelope` field and
/// the raw bytes that describe the email in `bytes`. Its body as an
/// `melib::email::Attachment` can be parsed on demand
/// with the `melib::email::Mail::body` method.
#[derive(Debug, Clone, Default)]
pub struct Mail {
pub envelope: Envelope,
@ -199,12 +207,13 @@ impl Mail {
crate::declare_u64_hash!(EnvelopeHash);
/// `Envelope` represents all the header and structure data of an email we need to know.
/// `Envelope` represents all the header and structure data of an email we need
/// to know.
///
/// Attachments (the email's body) is parsed on demand with `body` method.
///
///To access the email attachments, you need to parse them from the raw email bytes into an
///`Attachment` object.
///To access the email attachments, you need to parse them from the raw email
/// bytes into an `Attachment` object.
#[derive(Clone, Serialize, Deserialize)]
pub struct Envelope {
pub hash: EnvelopeHash,
@ -364,7 +373,11 @@ impl Envelope {
self.has_attachments =
Attachment::check_if_has_attachments_quick(body, boundary);
} else {
debug!("{:?} has no boundary field set in multipart/mixed content-type field.", &self);
debug!(
"{:?} has no boundary field set in multipart/mixed content-type \
field.",
&self
);
}
}
_ => {}

View File

@ -19,11 +19,15 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
//! Email addresses. Parsing functions are in [melib::email::parser::address](../parser/address/index.html).
//! Email addresses. Parsing functions are in
//! [melib::email::parser::address](../parser/address/index.html).
use std::{
collections::HashSet,
convert::TryFrom,
hash::{Hash, Hasher},
};
use super::*;
use std::collections::HashSet;
use std::convert::TryFrom;
use std::hash::{Hash, Hasher};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct GroupAddress {
@ -53,7 +57,7 @@ pub struct GroupAddress {
* > display_name
* >
* > address_spec
*```
* ```
*/
pub struct MailboxAddress {
pub raw: Vec<u8>,
@ -78,14 +82,20 @@ impl PartialEq for MailboxAddress {
///
/// ```rust
/// # use melib::email::Address;
/// let addr = Address::new(Some("Jörg Doe".to_string()), "joerg@example.com".to_string());
/// let addr = Address::new(
/// Some("Jörg Doe".to_string()),
/// "joerg@example.com".to_string(),
/// );
/// assert_eq!(addr.to_string().as_str(), "Jörg Doe <joerg@example.com>");
/// ```
///
/// or parse it from a raw value:
///
/// ```rust
/// let (rest_bytes, addr) = melib::email::parser::address::address("=?utf-8?q?J=C3=B6rg_Doe?= <joerg@example.com>".as_bytes()).unwrap();
/// let (rest_bytes, addr) = melib::email::parser::address::address(
/// "=?utf-8?q?J=C3=B6rg_Doe?= <joerg@example.com>".as_bytes(),
/// )
/// .unwrap();
/// assert!(rest_bytes.is_empty());
/// assert_eq!(addr.get_display_name(), Some("Jörg Doe".to_string()));
/// assert_eq!(addr.get_email(), "joerg@example.com".to_string());
@ -154,8 +164,8 @@ impl Address {
/// Get the display name of this address.
///
/// If it's a group, it's the name of the group. Otherwise it's the `display_name` part of
/// the mailbox:
/// If it's a group, it's the name of the group. Otherwise it's the
/// `display_name` part of the mailbox:
///
///
/// ```text
@ -166,7 +176,7 @@ impl Address {
/// display_name │ display_name │
/// │ │
/// address_spec address_spec
///```
/// ```
pub fn get_display_name(&self) -> Option<String> {
let ret = match self {
Address::Mailbox(m) => m.display_name.display(&m.raw),
@ -179,7 +189,8 @@ impl Address {
}
}
/// Get the address spec part of this address. A group returns an empty `String`.
/// Get the address spec part of this address. A group returns an empty
/// `String`.
pub fn get_email(&self) -> String {
match self {
Address::Mailbox(m) => m.address_spec.display(&m.raw),
@ -238,8 +249,8 @@ impl Address {
/// Get subaddress out of an address (e.g. `ken+subaddress@example.org`).
///
/// Subaddresses are commonly text following a "+" character in an email address's local part
/// . They are defined in [RFC5233 `Sieve Email Filtering: Subaddress Extension`](https://tools.ietf.org/html/rfc5233.html)
/// Subaddresses are commonly text following a "+" character in an email
/// address's local part . They are defined in [RFC5233 `Sieve Email Filtering: Subaddress Extension`](https://tools.ietf.org/html/rfc5233.html)
///
/// # Examples
///

View File

@ -18,11 +18,15 @@
* You should have received a copy of the GNU General Public License
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use crate::email::attachments::{Attachment, AttachmentBuilder};
use crate::email::parser::BytesExt;
use std::{
fmt::{Display, Formatter, Result as FmtResult},
str,
};
use std::fmt::{Display, Formatter, Result as FmtResult};
use std::str;
use crate::email::{
attachments::{Attachment, AttachmentBuilder},
parser::BytesExt,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum Charset {
@ -421,9 +425,10 @@ impl ContentType {
boundary.push_str(&random_boundary);
/* rfc134
* "The only mandatory parameter for the multipart Content-Type is the boundary parameter,
* which consists of 1 to 70 characters from a set of characters known to be very robust
* through email gateways, and NOT ending with white space"*/
* "The only mandatory parameter for the multipart Content-Type is the
* boundary parameter, which consists of 1 to 70 characters from a
* set of characters known to be very robust through email gateways,
* and NOT ending with white space" */
boundary.truncate(70);
boundary
}

View File

@ -20,17 +20,17 @@
*/
/*! Encoding/decoding of attachments */
use crate::email::{
address::StrBuilder,
parser::{self, BytesExt},
Mail,
};
use core::fmt;
use core::str;
use core::{fmt, str};
use data_encoding::BASE64_MIME;
use smallvec::SmallVec;
use crate::email::attachment_types::*;
use crate::email::{
address::StrBuilder,
attachment_types::*,
parser::{self, BytesExt},
Mail,
};
pub type Filter<'a> = Box<dyn FnMut(&Attachment, &mut Vec<u8>) + 'a>;
@ -117,8 +117,9 @@ impl AttachmentBuilder {
self
}
/// Set body to the entire raw contents, use this if raw contains only data and no headers
/// If raw contains data and headers pass it through AttachmentBuilder::new().
/// Set body to the entire raw contents, use this if raw contains only data
/// and no headers If raw contains data and headers pass it through
/// AttachmentBuilder::new().
pub fn set_body_to_raw(&mut self) -> &mut Self {
self.body = StrBuilder {
offset: 0,
@ -515,8 +516,8 @@ impl Attachment {
}
}
/* Call on the body of a multipart/mixed Envelope to check if there are attachments without
* completely parsing them */
/* Call on the body of a multipart/mixed Envelope to check if there are
* attachments without completely parsing them */
pub fn check_if_has_attachments_quick(bytes: &[u8], boundary: &[u8]) -> bool {
if bytes.is_empty() {
return false;

View File

@ -20,19 +20,25 @@
*/
/*! Compose a `Draft`, with MIME and attachment support */
use super::*;
use crate::email::attachment_types::{
Charset, ContentTransferEncoding, ContentType, MultipartType,
use std::{
ffi::OsStr,
io::Read,
path::{Path, PathBuf},
str::FromStr,
};
use crate::email::attachments::AttachmentBuilder;
use crate::shellexpand::ShellExpandTrait;
use data_encoding::BASE64_MIME;
use std::ffi::OsStr;
use std::io::Read;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use xdg_utils::query_mime_info;
use super::*;
use crate::{
email::{
attachment_types::{Charset, ContentTransferEncoding, ContentType, MultipartType},
attachments::AttachmentBuilder,
},
shellexpand::ShellExpandTrait,
};
pub mod mime;
pub mod random;
@ -370,7 +376,10 @@ fn build_multipart(
}
ret.push_str("\r\n\r\n");
/* rfc1341 */
ret.push_str("This is a MIME formatted message with attachments. Use a MIME-compliant client to view it properly.\r\n");
ret.push_str(
"This is a MIME formatted message with attachments. Use a MIME-compliant client to view \
it properly.\r\n",
);
for sub in parts {
ret.push_str("--");
ret.push_str(&boundary);
@ -484,9 +493,10 @@ fn print_attachment(ret: &mut String, a: AttachmentBuilder) {
#[cfg(test)]
mod tests {
use super::*;
use std::str::FromStr;
use super::*;
#[test]
fn test_new_draft() {
let mut default = Draft::default();
@ -508,21 +518,33 @@ mod tests {
let original = default.clone();
let s = default.to_edit_string();
assert_eq!(s, "<!--\nDate: Sun, 16 Jun 2013 17:56:45 +0200\nFrom: \nTo: \nCc: \nBcc: \nSubject: test_update()\n-->\n\nαδφαφσαφασ");
assert_eq!(
s,
"<!--\nDate: Sun, 16 Jun 2013 17:56:45 +0200\nFrom: \nTo: \nCc: \nBcc: \nSubject: \
test_update()\n-->\n\nαδφαφσαφασ"
);
assert!(!default.update(&s).unwrap());
assert_eq!(&original, &default);
default.set_wrap_header_preamble(Some(("".to_string(), "".to_string())));
let original = default.clone();
let s = default.to_edit_string();
assert_eq!(s, "Date: Sun, 16 Jun 2013 17:56:45 +0200\nFrom: \nTo: \nCc: \nBcc: \nSubject: test_update()\n\nαδφαφσαφασ");
assert_eq!(
s,
"Date: Sun, 16 Jun 2013 17:56:45 +0200\nFrom: \nTo: \nCc: \nBcc: \nSubject: \
test_update()\n\nαδφαφσαφασ"
);
assert!(!default.update(&s).unwrap());
assert_eq!(&original, &default);
default.set_wrap_header_preamble(None);
let original = default.clone();
let s = default.to_edit_string();
assert_eq!(s, "Date: Sun, 16 Jun 2013 17:56:45 +0200\nFrom: \nTo: \nCc: \nBcc: \nSubject: test_update()\n\nαδφαφσαφασ");
assert_eq!(
s,
"Date: Sun, 16 Jun 2013 17:56:45 +0200\nFrom: \nTo: \nCc: \nBcc: \nSubject: \
test_update()\n\nαδφαφσαφασ"
);
assert!(!default.update(&s).unwrap());
assert_eq!(&original, &default);
@ -532,7 +554,11 @@ mod tests {
)));
let original = default.clone();
let s = default.to_edit_string();
assert_eq!(s, "{-\n\n\n===========\nDate: Sun, 16 Jun 2013 17:56:45 +0200\nFrom: \nTo: \nCc: \nBcc: \nSubject: test_update()\n</mixed>\n\nαδφαφσαφασ");
assert_eq!(
s,
"{-\n\n\n===========\nDate: Sun, 16 Jun 2013 17:56:45 +0200\nFrom: \nTo: \nCc: \nBcc: \
\nSubject: test_update()\n</mixed>\n\nαδφαφσαφασ"
);
assert!(!default.update(&s).unwrap());
assert_eq!(&original, &default);
@ -543,7 +569,11 @@ mod tests {
.set_wrap_header_preamble(Some(("<!--".to_string(), "-->".to_string())));
let original = default.clone();
let s = default.to_edit_string();
assert_eq!(s, "<!--\nDate: Sun, 16 Jun 2013 17:56:45 +0200\nFrom: \nTo: \nCc: \nBcc: \nSubject: test_update()\n-->\n\nhellohello<!--\n<!--\n<--hellohello\nhellohello-->\n-->\n-->hello\n");
assert_eq!(
s,
"<!--\nDate: Sun, 16 Jun 2013 17:56:45 +0200\nFrom: \nTo: \nCc: \nBcc: \nSubject: \
test_update()\n-->\n\nhellohello<!--\n<!--\n<--hellohello\nhellohello-->\n-->\n-->hello\n"
);
assert!(!default.update(&s).unwrap());
assert_eq!(&original, &default);
}
@ -572,7 +602,8 @@ mod tests {
*/
}
/// Reads file from given path, and returns an 'application/octet-stream' AttachmentBuilder object
/// Reads file from given path, and returns an 'application/octet-stream'
/// AttachmentBuilder object
pub fn attachment_from_file<I>(path: &I) -> Result<AttachmentBuilder>
where
I: AsRef<OsStr>,

View File

@ -20,7 +20,6 @@
*/
use super::*;
#[cfg(feature = "unicode_algorithms")]
use crate::text_processing::grapheme_clusters::TextProcessing;
@ -61,9 +60,9 @@ pub fn encode_header(value: &str) -> String {
is_current_window_ascii = false;
}
/* RFC2047 recommends:
* 'While there is no limit to the length of a multiple-line header field, each line of
* a header field that contains one or more 'encoded-word's is limited to 76
* characters.'
* 'While there is no limit to the length of a multiple-line header field, each
* line of a header field that contains one or more
* 'encoded-word's is limited to 76 characters.'
* This is a rough compliance.
*/
(false, false) if (((4 * (idx - current_window_start) / 3) + 3) & !3) > 33 => {
@ -84,8 +83,8 @@ pub fn encode_header(value: &str) -> String {
}
#[cfg(not(feature = "unicode_algorithms"))]
{
/* TODO: test this. If it works as fine as the one above, there's no need to keep the above
* implementation.*/
/* TODO: test this. If it works as fine as the one above, there's no need to
* keep the above implementation. */
for (i, g) in value.char_indices() {
match (g.is_ascii(), is_current_window_ascii) {
(true, true) => {
@ -116,9 +115,9 @@ pub fn encode_header(value: &str) -> String {
is_current_window_ascii = false;
}
/* RFC2047 recommends:
* 'While there is no limit to the length of a multiple-line header field, each line of
* a header field that contains one or more 'encoded-word's is limited to 76
* characters.'
* 'While there is no limit to the length of a multiple-line header field, each
* line of a header field that contains one or more
* 'encoded-word's is limited to 76 characters.'
* This is a rough compliance.
*/
(false, false)
@ -139,8 +138,8 @@ pub fn encode_header(value: &str) -> String {
}
}
}
/* If the last part of the header value is encoded, it won't be pushed inside the previous for
* block */
/* If the last part of the header value is encoded, it won't be pushed inside
* the previous for block */
if !is_current_window_ascii {
ret.push_str(&format!(
"=?UTF-8?B?{}?=",
@ -156,35 +155,39 @@ fn test_encode_header() {
let words = "compilers/2020a σε Rust";
assert_eq!(
"compilers/2020a =?UTF-8?B?z4POtSA=?=Rust",
&encode_header(&words),
&encode_header(words),
);
assert_eq!(
&std::str::from_utf8(
&crate::email::parser::encodings::phrase(encode_header(&words).as_bytes(), false)
&crate::email::parser::encodings::phrase(encode_header(words).as_bytes(), false)
.unwrap()
.1
)
.unwrap(),
&words,
);
let words = "[internal] =?UTF-8?B?zp3Orc6/z4Igzp/OtM63zrPPjM+CIM6jz4U=?= =?UTF-8?B?zrPOs8+BzrHPhs6uz4I=?=";
let words = "[internal] =?UTF-8?B?zp3Orc6/z4Igzp/OtM63zrPPjM+CIM6jz4U=?= \
=?UTF-8?B?zrPOs8+BzrHPhs6uz4I=?=";
let words_enc = r#"[internal] Νέος Οδηγός Συγγραφής"#;
assert_eq!(words, &encode_header(&words_enc),);
assert_eq!(words, &encode_header(words_enc),);
assert_eq!(
r#"[internal] Νέος Οδηγός Συγγραφής"#,
std::str::from_utf8(
&crate::email::parser::encodings::phrase(encode_header(&words_enc).as_bytes(), false)
&crate::email::parser::encodings::phrase(encode_header(words_enc).as_bytes(), false)
.unwrap()
.1
)
.unwrap(),
);
//let words = "[Advcomparch] =?utf-8?b?zqPPhc68z4DOtc+BzrnPhs6/z4HOrCDPg861IGZs?=\n\t=?utf-8?b?dXNoIM67z4zOs8+JIG1pc3ByZWRpY3Rpb24gzrrOsc+Ezqwgz4TOt869?=\n\t=?utf-8?b?IM61zrrPhM6tzrvOtc+Dzrcgc3RvcmU=?=";
//let words = "[Advcomparch]
// =?utf-8?b?zqPPhc68z4DOtc+BzrnPhs6/z4HOrCDPg861IGZs?=\n\t=?utf-8?b?
// dXNoIM67z4zOs8+JIG1pc3ByZWRpY3Rpb24gzrrOsc+Ezqwgz4TOt869?=\n\t=?utf-8?b?
// IM61zrrPhM6tzrvOtc+Dzrcgc3RvcmU=?=";
let words_enc = "[Advcomparch] Συμπεριφορά σε flush λόγω misprediction κατά την εκτέλεση store";
assert_eq!(
"[Advcomparch] Συμπεριφορά σε flush λόγω misprediction κατά την εκτέλεση store",
std::str::from_utf8(
&crate::email::parser::encodings::phrase(encode_header(&words_enc).as_bytes(), false)
&crate::email::parser::encodings::phrase(encode_header(words_enc).as_bytes(), false)
.unwrap()
.1
)

View File

@ -19,10 +19,7 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use std::char;
use std::fs::File;
use std::io::prelude::*;
use std::time::SystemTime;
use std::{char, fs::File, io::prelude::*, time::SystemTime};
fn random_u64() -> u64 {
let mut f = File::open("/dev/urandom").unwrap();

View File

@ -20,20 +20,25 @@
*/
/*! Wrapper type `HeaderName` for case-insensitive comparisons */
use crate::error::Error;
use std::{
borrow::Borrow,
cmp::{Eq, PartialEq},
convert::TryFrom,
fmt,
hash::{Hash, Hasher},
ops::{Deref, DerefMut},
};
use indexmap::IndexMap;
use smallvec::SmallVec;
use std::borrow::Borrow;
use std::cmp::{Eq, PartialEq};
use std::convert::TryFrom;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::ops::{Deref, DerefMut};
use crate::error::Error;
#[derive(Clone, Copy, Serialize, Deserialize)]
pub struct HeaderNameType<S>(S);
///Case insensitive wrapper for a header name. As of `RFC5322` it's guaranteened to be ASCII.
/// Case insensitive wrapper for a header name. As of `RFC5322` it's
/// guaranteed to be ASCII.
pub type HeaderName = HeaderNameType<SmallVec<[u8; 32]>>;
impl HeaderName {
@ -148,7 +153,7 @@ impl<'a> Borrow<dyn HeaderKey + 'a> for HeaderName {
impl<S: AsRef<[u8]>> HeaderNameType<S> {
pub fn as_str(&self) -> &str {
//HeadersType are ascii so valid utf8
// HeadersType are ascii so valid utf8
unsafe { std::str::from_utf8_unchecked(self.0.as_ref()) }
}

View File

@ -20,11 +20,12 @@
*/
/*! Parsing of rfc2369/rfc2919 `List-*` headers */
use super::parser;
use super::Envelope;
use smallvec::SmallVec;
use std::convert::From;
use smallvec::SmallVec;
use super::{parser, Envelope};
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum ListAction<'a> {
Url(&'a [u8]),
@ -43,8 +44,8 @@ impl<'a> From<&'a [u8]> for ListAction<'a> {
} else if value.starts_with(b"NO") {
ListAction::No
} else {
/* Otherwise treat it as url. There's no foolproof way to check if this is valid, so
* postpone it until we try an HTTP request.
/* Otherwise treat it as url. There's no foolproof way to check if this is
* valid, so postpone it until we try an HTTP request.
*/
ListAction::Url(value)
}
@ -55,8 +56,8 @@ impl<'a> ListAction<'a> {
pub fn parse_options_list(input: &'a [u8]) -> Option<SmallVec<[ListAction<'a>; 4]>> {
parser::mailing_lists::rfc_2369_list_headers_action_list(input)
.map(|(_, mut vec)| {
/* Prefer email options first, since this _is_ a mail client after all and it's
* more automated */
/* Prefer email options first, since this _is_ a mail client after all and
* it's more automated */
vec.sort_unstable_by(|a, b| {
match (a.starts_with(b"mailto:"), b.starts_with(b"mailto:")) {
(true, false) => std::cmp::Ordering::Less,

View File

@ -20,9 +20,10 @@
*/
/*! Parsing of `mailto` addresses */
use super::*;
use std::convert::TryFrom;
use super::*;
#[derive(Debug, Clone)]
pub struct Mailto {
pub address: Address,

View File

@ -20,20 +20,21 @@
*/
/*! Parsers for email. See submodules */
use crate::error::{Error, Result, ResultIntoError};
use std::borrow::Cow;
use nom::{
branch::alt,
bytes::complete::{is_a, is_not, tag, take, take_until, take_while, take_while1},
character::{is_alphabetic, is_digit, is_hex_digit},
combinator::peek,
combinator::{map, opt},
combinator::{map, opt, peek},
error::{context, ErrorKind},
multi::{many0, many1, separated_list1},
number::complete::le_u8,
sequence::{delimited, pair, preceded, separated_pair, terminated},
};
use smallvec::SmallVec;
use std::borrow::Cow;
use crate::error::{Error, Result, ResultIntoError};
macro_rules! to_str {
($l:expr) => {{
@ -318,8 +319,7 @@ pub fn mail(input: &[u8]) -> Result<(Vec<(&[u8], &[u8])>, &[u8])> {
pub mod dates {
/*! Date values in headers */
use super::generic::*;
use super::*;
use super::{generic::*, *};
use crate::datetime::UnixTimestamp;
fn take_n_digits(n: usize) -> impl Fn(&[u8]) -> IResult<&[u8], &[u8]> {
@ -451,15 +451,15 @@ pub mod dates {
///e.g Wed Sep 9 00:27:54 2020
///```text
///day-of-week month day time year
///date-time = [ day-of-week "," ] date time [CFWS]
///date = day month year
///time = time-of-day zone
///time-of-day = hour ":" minute [ ":" second ]
///hour = 2DIGIT / obs-hour
///minute = 2DIGIT / obs-minute
///second = 2DIGIT / obs-second
///```
/// day-of-week month day time year
/// date-time = [ day-of-week "," ] date time [CFWS]
/// date = day month year
/// time = time-of-day zone
/// time-of-day = hour ":" minute [ ":" second ]
/// hour = 2DIGIT / obs-hour
/// minute = 2DIGIT / obs-minute
/// second = 2DIGIT / obs-second
/// ```
pub fn mbox_date_time(input: &[u8]) -> IResult<&[u8], UnixTimestamp> {
let orig_input = input;
let mut accum: SmallVec<[u8; 32]> = SmallVec::new();
@ -656,7 +656,8 @@ pub mod generic {
let (rest, _) = utf8_tail(rest)?;
Ok((rest, &input[0..2]))
}
/// UTF8-3 = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) / %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail )
/// UTF8-3 = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) / %xED
/// %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail )
fn utf8_3<'a>(input: &'a [u8]) -> IResult<&'a [u8], &'a [u8]> {
alt((
|input: &'a [u8]| -> IResult<&'a [u8], &'a [u8]> {
@ -685,7 +686,8 @@ pub mod generic {
},
))(input)
}
/// UTF8-4 = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) / %xF4 %x80-8F 2( UTF8-tail )
/// UTF8-4 = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) /
/// %xF4 %x80-8F 2( UTF8-tail )
fn utf8_4<'a>(input: &'a [u8]) -> IResult<&'a [u8], &'a [u8]> {
alt((
|input: &'a [u8]| -> IResult<&'a [u8], &'a [u8]> {
@ -741,11 +743,11 @@ pub mod generic {
}
///```text
///ctext = %d33-39 / ; Printable US-ASCII
/// ctext = %d33-39 / ; Printable US-ASCII
/// %d42-91 / ; characters not including
/// %d93-126 / ; "(", ")", or "\"
/// obs-ctext
///```
/// ```
fn ctext(input: &[u8]) -> IResult<&[u8], ()> {
alt((
map(
@ -761,13 +763,13 @@ pub mod generic {
}
///```text
///ctext = %d33-39 / ; Printable US-ASCII
/// ctext = %d33-39 / ; Printable US-ASCII
/// %d42-91 / ; characters not including
/// %d93-126 / ; "(", ")", or "\"
/// obs-ctext
///ccontent = ctext / quoted-pair / comment
///comment = "(" *([FWS] ccontent) [FWS] ")"
///```
/// ccontent = ctext / quoted-pair / comment
/// comment = "(" *([FWS] ccontent) [FWS] ")"
/// ```
pub fn comment(input: &[u8]) -> IResult<&[u8], ()> {
if !input.starts_with(b"(") {
return Err(nom::Err::Error(
@ -911,8 +913,7 @@ pub mod generic {
}
}
use crate::email::address::Address;
use crate::email::mailto::Mailto;
use crate::email::{address::Address, mailto::Mailto};
pub fn mailto(mut input: &[u8]) -> IResult<&[u8], Mailto> {
if !input.starts_with(b"mailto:") {
return Err(nom::Err::Error(
@ -1081,7 +1082,8 @@ pub mod generic {
Ok((rest, ret))
}
///`quoted-string = [CFWS] DQUOTE *([FWS] qcontent) [FWS] DQUOTE [CFWS]`
///`quoted-string = [CFWS] DQUOTE *([FWS] qcontent) [FWS] DQUOTE
/// [CFWS]`
pub fn quoted_string(input: &[u8]) -> IResult<&[u8], Cow<'_, [u8]>> {
let (input, opt_space) = opt(cfws)(input)?;
if !input.starts_with(b"\"") {
@ -1213,7 +1215,10 @@ pub mod generic {
Ok((input, ret.into()))
}
///`atext = ALPHA / DIGIT / ; Printable US-ASCII "!" / "#" / ; characters not including "$" / "%" / ; specials. Used for atoms. "&" / "'" / "*" / "+" / "-" / "/" / "=" / "?" / "^" / "_" / "`" / "{" / "|" / "}" / "~"`
///`atext = ALPHA / DIGIT / ; Printable US-ASCII "!" / "#" /
/// ; characters not including "$" / "%" / ; specials. Used for
/// atoms. "&" / "'" / "*" / "+" / "-" / "/" / "=" / "?" / "^" / "_" / "`"
/// / "{" / "|" / "}" / "~"`
pub fn atext_ascii(input: &[u8]) -> IResult<&[u8], Cow<'_, [u8]>> {
if input.is_empty() {
return Err(nom::Err::Error((input, "atext(): empty input").into()));
@ -1244,10 +1249,10 @@ pub mod generic {
}
///```text
///dtext = %d33-90 / ; Printable US-ASCII
/// dtext = %d33-90 / ; Printable US-ASCII
/// %d94-126 / ; characters not including
/// obs-dtext ; "[", "]", or "\"
///```
/// ```
pub fn dtext(input: &[u8]) -> IResult<&[u8], u8> {
alt((byte_in_range(33, 90), byte_in_range(94, 125)))(input)
}
@ -1259,11 +1264,13 @@ pub mod mailing_lists {
//! Implemented RFCs:
//!
//! - [RFC2369 "The Use of URLs as Meta-Syntax for Core Mail List Commands and their Transport through Message Header Fields"](https://tools.ietf.org/html/rfc2369)
use super::*;
use generic::cfws;
///Parse the value of headers defined in RFC2369 "The Use of URLs as Meta-Syntax for Core
///Mail List Commands and their Transport through Message Header Fields"
use super::*;
///Parse the value of headers defined in RFC2369 "The Use of URLs as
/// Meta-Syntax for Core Mail List Commands and their Transport through
/// Message Header Fields"
pub fn rfc_2369_list_headers_action_list(input: &[u8]) -> IResult<&[u8], Vec<&[u8]>> {
let (input, _) = opt(cfws)(input)?;
let (input, ret) = alt((
@ -1458,9 +1465,9 @@ pub mod headers {
/* A header can span multiple lines, eg:
*
* Received: from -------------------- (-------------------------)
* by --------------------- (--------------------- [------------------]) (-----------------------)
* with ESMTP id ------------ for <------------------->;
* Tue, 5 Jan 2016 21:30:44 +0100 (CET)
* by --------------------- (--------------------- [------------------])
* (-----------------------) with ESMTP id ------------ for
* <------------------->; Tue, 5 Jan 2016 21:30:44 +0100 (CET)
*/
pub fn header_value(input: &[u8]) -> IResult<&[u8], &[u8]> {
@ -1580,8 +1587,10 @@ pub mod headers {
pub mod attachments {
/*! Email attachments */
use super::*;
use crate::email::address::*;
use crate::email::attachment_types::{ContentDisposition, ContentDispositionKind};
use crate::email::{
address::*,
attachment_types::{ContentDisposition, ContentDispositionKind},
};
pub fn attachment(input: &[u8]) -> IResult<&[u8], (std::vec::Vec<(&[u8], &[u8])>, &[u8])> {
alt((
separated_pair(
@ -1807,7 +1816,8 @@ pub mod attachments {
pub fn content_disposition(input: &[u8]) -> IResult<&[u8], ContentDisposition> {
let (input, kind) = alt((take_until(";"), take_while(|_| true)))(input.trim())?;
let mut ret = ContentDisposition {
/* RFC2183 Content-Disposition: "Unrecognized disposition types should be treated as `attachment'." */
/* RFC2183 Content-Disposition: "Unrecognized disposition types should be treated as
* `attachment'." */
kind: if kind.trim().eq_ignore_ascii_case(b"inline") {
ContentDispositionKind::Inline
} else {
@ -1846,11 +1856,11 @@ pub mod attachments {
pub mod encodings {
/*! Email encodings (quoted printable, MIME) */
use data_encoding::BASE64_MIME;
use encoding::{all::*, DecoderTrap, Encoding};
use super::*;
use crate::email::attachment_types::Charset;
use data_encoding::BASE64_MIME;
use encoding::all::*;
use encoding::{DecoderTrap, Encoding};
pub fn quoted_printable_byte(input: &[u8]) -> IResult<&[u8], u8> {
if input.len() < 3 {
Err(nom::Err::Error(
@ -2023,7 +2033,8 @@ pub mod encodings {
if input.starts_with(b"=\n") {
Ok((&input[2..], input[1])) // `=\n` is an escaped space character.
} else if input.starts_with(b"=\r\n") {
Ok((&input[3..], input[2])) // `=\r\n` is an escaped space character.
Ok((&input[3..], input[2])) // `=\r\n` is an escaped space
// character.
} else {
Err(nom::Err::Error(
(input, "quoted_printable_soft_break(): invalid input").into(),
@ -2036,8 +2047,9 @@ pub mod encodings {
Ok((rest, 0x20))
}
// With MIME, headers in quoted printable format can contain underscores that represent spaces.
// In non-header context, an underscore is just a plain underscore.
// With MIME, headers in quoted printable format can contain underscores that
// represent spaces. In non-header context, an underscore is just a plain
// underscore.
pub fn quoted_printable_bytes_header(input: &[u8]) -> IResult<&[u8], Vec<u8>> {
many0(alt((quoted_printable_byte, qp_underscore_header, le_u8)))(input)
}
@ -2173,9 +2185,9 @@ pub mod address {
//! - [RFC6532 "Internationalized Email Headers"](https://tools.ietf.org/html/rfc6532)
//! - [RFC2047 "MIME Part Three: Message Header Extensions for Non-ASCII Text"](https://tools.ietf.org/html/rfc2047)
use super::*;
use crate::email::address::*;
use crate::email::parser::generic::{
atom, cfws, dot_atom, dot_atom_text, dtext, phrase2, quoted_string,
use crate::email::{
address::*,
parser::generic::{atom, cfws, dot_atom, dot_atom_text, dtext, phrase2, quoted_string},
};
pub fn display_addr(input: &[u8]) -> IResult<&[u8], Address> {
if input.is_empty() || input.len() < 3 {
@ -2447,8 +2459,8 @@ pub mod address {
}
///```text
///address = mailbox / group
///```
/// address = mailbox / group
/// ```
pub fn address(input: &[u8]) -> IResult<&[u8], Address> {
alt((mailbox, group))(input)
}
@ -2599,15 +2611,18 @@ pub mod address {
#[cfg(test)]
mod tests {
use super::{address::*, encodings::*, *};
use crate::email::address::*;
use crate::make_address;
use crate::{email::address::*, make_address};
#[test]
fn test_phrase() {
let words = b"=?iso-8859-7?B?W215Y291cnNlcy5udHVhLmdyIC0gyvXs4fTp6t4g6uHpIMri4e306ere?=
=?iso-8859-7?B?INb18+nq3l0gzd3hIMHt4erv3+358+c6IMzF0c/TIMHQz9TFy8XTzMHU?=
=?iso-8859-7?B?2c0gwiDUzC4gysHNLiDFzsXUwdPH0yAyMDE3LTE4OiDTx8zFydnTxw==?=";
assert_eq!("[mycourses.ntua.gr - Κυματική και Κβαντική Φυσική] Νέα Ανακοίνωση: ΜΕΡΟΣ ΑΠΟΤΕΛΕΣΜΑΤΩΝ Β ΤΜ. ΚΑΝ. ΕΞΕΤΑΣΗΣ 2017-18: ΣΗΜΕΙΩΣΗ" , std::str::from_utf8(&phrase(words.trim(), false).unwrap().1).unwrap());
assert_eq!(
"[mycourses.ntua.gr - Κυματική και Κβαντική Φυσική] Νέα Ανακοίνωση: ΜΕΡΟΣ \
ΑΠΟΤΕΛΕΣΜΑΤΩΝ Β ΤΜ. ΚΑΝ. ΕΞΕΤΑΣΗΣ 2017-18: ΣΗΜΕΙΩΣΗ",
std::str::from_utf8(&phrase(words.trim(), false).unwrap().1).unwrap()
);
let words = b"=?UTF-8?Q?=CE=A0=CF=81=CF=8C=CF=83=CE=B8=CE=B5?= =?UTF-8?Q?=CF=84=CE=B7_=CE=B5=CE=BE=CE=B5=CF=84?= =?UTF-8?Q?=CE=B1=CF=83=CF=84=CE=B9=CE=BA=CE=AE?=";
assert_eq!(
"Πρόσθετη εξεταστική",
@ -2929,12 +2944,16 @@ mod tests {
"=?iso-8859-1?q?Fran=E7ois?= Pons <fpons@mandrakesoft.com>"
);
assert_parse!(
"هل تتكلم اللغة الإنجليزية /العربية؟", "do.you.speak@arabic.com",
"=?utf-8?b?2YfZhCDYqtiq2YPZhNmFINin2YTZhNi62Kkg2KfZhNil2YbYrNmE2YrYstmK2Kk=?=\n =?utf-8?b?IC/Yp9mE2LnYsdio2YrYqdif?= <do.you.speak@arabic.com>"
"هل تتكلم اللغة الإنجليزية /العربية؟",
"do.you.speak@arabic.com",
"=?utf-8?b?2YfZhCDYqtiq2YPZhNmFINin2YTZhNi62Kkg2KfZhNil2YbYrNmE2YrYstmK2Kk=?=\n \
=?utf-8?b?IC/Yp9mE2LnYsdio2YrYqdif?= <do.you.speak@arabic.com>"
);
assert_parse!(
"狂ったこの世で狂うなら気は確かだ。", "famous@quotes.ja",
"=?utf-8?b?54uC44Gj44Gf44GT44Gu5LiW44Gn54uC44GG44Gq44KJ5rCX44Gv56K644GL44Gg?=\n =?utf-8?b?44CC?= <famous@quotes.ja>"
"狂ったこの世で狂うなら気は確かだ。",
"famous@quotes.ja",
"=?utf-8?b?54uC44Gj44Gf44GT44Gu5LiW44Gn54uC44GG44Gq44KJ5rCX44Gv56K644GL44Gg?=\n \
=?utf-8?b?44CC?= <famous@quotes.ja>"
);
assert_eq!(
Address::new_group(

View File

@ -20,11 +20,13 @@
*/
/*! Verification of OpenPGP signatures */
use crate::email::{
attachment_types::{ContentType, MultipartType},
attachments::Attachment,
use crate::{
email::{
attachment_types::{ContentType, MultipartType},
attachments::Attachment,
},
Error, Result,
};
use crate::{Error, Result};
/// Convert raw attachment to the form needed for signature verification ([rfc3156](https://tools.ietf.org/html/rfc3156))
///

View File

@ -23,13 +23,7 @@
* An error object for `melib`
*/
use std::borrow::Cow;
use std::fmt;
use std::io;
use std::result;
use std::str;
use std::string;
use std::sync::Arc;
use std::{borrow::Cow, fmt, io, result, str, string, sync::Arc};
pub type Result<T> = result::Result<T, Error>;

View File

@ -195,8 +195,7 @@ pub struct gpgme_data {
}
pub type gpgme_data_t = *mut gpgme_data;
pub type gpgme_error_t = gpg_error_t;
pub use self::gpg_err_code_t as gpgme_err_code_t;
pub use self::gpg_err_source_t as gpgme_err_source_t;
pub use self::{gpg_err_code_t as gpgme_err_code_t, gpg_err_source_t as gpgme_err_source_t};
pub type gpgme_strerror = extern "C" fn(err: gpgme_error_t) -> *const ::std::os::raw::c_char;
pub type gpgme_strerror_r = unsafe extern "C" fn(
err: gpg_error_t,
@ -5326,14 +5325,12 @@ pub type gpgme_op_assuan_transact = extern "C" fn(
pub type GpgmeCtx = gpgme_ctx_t;
pub type GpgmeData = gpgme_data_t;
pub type GpgmeError = gpgme_error_t;
pub use self::gpgme_attr_t as GpgmeAttr;
pub use self::gpgme_data_encoding_t as GpgmeDataEncoding;
pub use self::gpgme_hash_algo_t as GpgmeHashAlgo;
pub use self::gpgme_protocol_t as GpgmeProtocol;
pub use self::gpgme_pubkey_algo_t as GpgmePubKeyAlgo;
pub use self::gpgme_sig_mode_t as GpgmeSigMode;
pub use self::gpgme_sig_stat_t as GpgmeSigStat;
pub use self::gpgme_validity_t as GpgmeValidity;
pub use self::{
gpgme_attr_t as GpgmeAttr, gpgme_data_encoding_t as GpgmeDataEncoding,
gpgme_hash_algo_t as GpgmeHashAlgo, gpgme_protocol_t as GpgmeProtocol,
gpgme_pubkey_algo_t as GpgmePubKeyAlgo, gpgme_sig_mode_t as GpgmeSigMode,
gpgme_sig_stat_t as GpgmeSigStat, gpgme_validity_t as GpgmeValidity,
};
pub type GpgmeEngineInfo = gpgme_engine_info_t;
pub type GpgmeSubkey = gpgme_subkey_t;
pub type GpgmeKeySig = gpgme_key_sig_t;

View File

@ -19,9 +19,10 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use super::*;
use std::io::{self, Read, Seek, Write};
use super::*;
#[repr(C)]
struct TagData {
idx: usize,

View File

@ -19,26 +19,34 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use crate::email::{
pgp::{DecryptionMetadata, Recipient},
Address,
use std::{
borrow::Cow,
collections::HashMap,
ffi::{CStr, CString, OsStr},
future::Future,
io::Seek,
os::unix::{
ffi::OsStrExt,
io::{AsRawFd, RawFd},
},
path::Path,
sync::{Arc, Mutex},
};
use crate::error::{Error, ErrorKind, IntoError, Result, ResultIntoError};
use futures::FutureExt;
use serde::{
de::{self, Deserialize},
Deserializer, Serialize, Serializer,
};
use smol::Async;
use std::borrow::Cow;
use std::collections::HashMap;
use std::ffi::{CStr, CString, OsStr};
use std::future::Future;
use std::io::Seek;
use std::os::unix::ffi::OsStrExt;
use std::os::unix::io::{AsRawFd, RawFd};
use std::path::Path;
use std::sync::{Arc, Mutex};
use crate::{
email::{
pgp::{DecryptionMetadata, Recipient},
Address,
},
error::{Error, ErrorKind, IntoError, Result, ResultIntoError},
};
macro_rules! call {
($lib:expr, $func:ty) => {{
@ -247,10 +255,10 @@ impl Context {
let mut io_cbs = gpgme_io_cbs {
add: Some(io::gpgme_register_io_cb),
add_priv: Arc::into_raw(add_priv_data) as *mut ::std::os::raw::c_void, //add_priv: *mut ::std::os::raw::c_void,
add_priv: Arc::into_raw(add_priv_data) as *mut ::std::os::raw::c_void, /* add_priv: *mut ::std::os::raw::c_void, */
remove: Some(io::gpgme_remove_io_cb),
event: Some(io::gpgme_event_io_cb),
event_priv: Arc::into_raw(event_priv_data) as *mut ::std::os::raw::c_void, //pub event_priv: *mut ::std::os::raw::c_void,
event_priv: Arc::into_raw(event_priv_data) as *mut ::std::os::raw::c_void, /* pub event_priv: *mut ::std::os::raw::c_void, */
};
unsafe {
@ -1345,7 +1353,8 @@ impl Drop for Key {
// futures::executor::block_on(ctx.keylist().unwrap()).unwrap()
// );
// let cipher = ctx.new_data_file("/tmp/msg.asc").unwrap();
// let plain = futures::executor::block_on(ctx.decrypt(cipher).unwrap()).unwrap();
// let plain =
// futures::executor::block_on(ctx.decrypt(cipher).unwrap()).unwrap();
// println!(
// "buf: {}",
// String::from_utf8_lossy(&plain.into_bytes().unwrap())

View File

@ -20,19 +20,29 @@
*/
//! A crate that performs mail client operations such as
//! - Hold an [`Envelope`](./email/struct.Envelope.html) with methods convenient for mail client use. (see module [`email`](./email/index.html))
//! - Abstract through mail storages through the [`MailBackend`](./backends/trait.MailBackend.html) trait, and handle read/writes/updates through it. (see module [`backends`](./backends/index.html))
//! - Decode attachments (see module [`email::attachments`](./email/attachments/index.html))
//! - Hold an [`Envelope`](./email/struct.Envelope.html) with methods convenient
//! for mail client use. (see module [`email`](./email/index.html))
//! - Abstract through mail storages through the
//! [`MailBackend`](./backends/trait.MailBackend.html) trait, and handle
//! read/writes/updates through it. (see module
//! [`backends`](./backends/index.html))
//! - Decode attachments (see module
//! [`email::attachments`](./email/attachments/index.html))
//! - Create new mail (see [`email::Draft`](./email/compose/struct.Draft.html))
//! - Send mail with an SMTP client (see module [`smtp`](./smtp/index.html))
//! - Manage an `addressbook` i.e. have contacts (see module [`addressbook`](./addressbook/index.html))
//! - Build thread structures out of a list of mail via their `In-Reply-To` and `References` header values (see module [`thread`](./thread/index.html))
//! - Manage an `addressbook` i.e. have contacts (see module
//! [`addressbook`](./addressbook/index.html))
//! - Build thread structures out of a list of mail via their `In-Reply-To` and
//! `References` header values (see module [`thread`](./thread/index.html))
//!
//! Other exports are
//! - Basic mail account configuration to use with [`backends`](./backends/index.html) (see module [`conf`](./conf/index.html))
//! - Basic mail account configuration to use with
//! [`backends`](./backends/index.html) (see module
//! [`conf`](./conf/index.html))
//! - Parser combinators (see module [`parsec`](./parsec/index.html))
//! - A `ShellExpandTrait` to expand paths like a shell.
//! - A `debug` macro that works like `std::dbg` but for multiple threads. (see [`debug` macro](./macro.debug.html))
//! - A `debug` macro that works like `std::dbg` but for multiple threads. (see
//! [`debug` macro](./macro.debug.html))
#[macro_use]
pub mod dbg {
@ -102,8 +112,7 @@ pub use datetime::UnixTimestamp;
#[macro_use]
mod logging;
pub use self::logging::LoggingLevel::*;
pub use self::logging::*;
pub use self::logging::{LoggingLevel::*, *};
pub mod addressbook;
pub use addressbook::*;
@ -180,12 +189,15 @@ impl core::fmt::Display for Bytes {
pub use shellexpand::ShellExpandTrait;
pub mod shellexpand {
use smallvec::SmallVec;
use std::ffi::OsStr;
use std::os::unix::ffi::OsStrExt;
#[cfg(not(any(target_os = "netbsd", target_os = "macos")))]
use std::os::unix::io::AsRawFd;
use std::path::{Path, PathBuf};
use std::{
ffi::OsStr,
os::unix::ffi::OsStrExt,
path::{Path, PathBuf},
};
use smallvec::SmallVec;
pub trait ShellExpandTrait {
fn expand(&self) -> PathBuf;
@ -233,23 +245,20 @@ pub mod shellexpand {
let (prefix, _match) = if self.as_os_str().as_bytes().ends_with(b"/.") {
(self.components().as_path(), OsStr::from_bytes(b"."))
} else if self.exists() && (!force || self.as_os_str().as_bytes().ends_with(b"/")) {
return SmallVec::new();
} else {
if self.exists() && (!force || self.as_os_str().as_bytes().ends_with(b"/")) {
// println!("{} {:?}", self.display(), self.components().last());
return SmallVec::new();
let last_component = self
.components()
.last()
.map(|c| c.as_os_str())
.unwrap_or_else(|| OsStr::from_bytes(b""));
let prefix = if let Some(p) = self.parent() {
p
} else {
let last_component = self
.components()
.last()
.map(|c| c.as_os_str())
.unwrap_or_else(|| OsStr::from_bytes(b""));
let prefix = if let Some(p) = self.parent() {
p
} else {
return SmallVec::new();
};
(prefix, last_component)
}
return SmallVec::new();
};
(prefix, last_component)
};
let dir = match ::nix::dir::Dir::openat(
@ -322,10 +331,13 @@ pub mod shellexpand {
pos += dir[0].d_reclen as usize;
}
// https://github.com/romkatv/gitstatus/blob/caf44f7aaf33d0f46e6749e50595323c277e0908/src/dir.cc
// "It's tempting to bail here if n + sizeof(linux_dirent64) + 512 <= n. After all, there
// was enough space for another entry but SYS_getdents64 didn't write it, so this must be
// the end of the directory listing, right? Unfortunately, no. SYS_getdents64 is finicky.
// It sometimes writes a partial list of entries even if the full list would fit."
// "It's tempting to bail here if n + sizeof(linux_dirent64) +
// 512 <= n. After all, there was enough space
// for another entry but SYS_getdents64 didn't write it, so this
// must be the end of the directory listing,
// right? Unfortunately, no. SYS_getdents64 is finicky.
// It sometimes writes a partial list of entries even if the
// full list would fit."
}
entries
}
@ -420,8 +432,7 @@ macro_rules! declare_u64_hash {
impl $type_name {
#[inline(always)]
pub fn from_bytes(bytes: &[u8]) -> Self {
use std::collections::hash_map::DefaultHasher;
use std::hash::Hasher;
use std::{collections::hash_map::DefaultHasher, hash::Hasher};
let mut h = DefaultHasher::new();
h.write(bytes);
Self(h.finish())

View File

@ -19,11 +19,14 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use std::{
fs::OpenOptions,
io::{BufWriter, Write},
path::PathBuf,
sync::{Arc, Mutex},
};
use crate::shellexpand::ShellExpandTrait;
use std::fs::OpenOptions;
use std::io::{BufWriter, Write};
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
#[derive(Copy, Clone, PartialEq, PartialOrd, Hash, Debug, Serialize, Deserialize)]
pub enum LoggingLevel {

View File

@ -442,30 +442,17 @@ pub fn is_not<'a>(slice: &'static [u8]) -> impl Parser<'a, &'a str> {
///
/// let parser = |input| {
/// alt([
/// delimited(
/// match_literal("{"),
/// quoted_slice(),
/// match_literal("}"),
/// ),
/// delimited(
/// match_literal("["),
/// quoted_slice(),
/// match_literal("]"),
/// ),
/// ]).parse(input)
/// delimited(match_literal("{"), quoted_slice(), match_literal("}")),
/// delimited(match_literal("["), quoted_slice(), match_literal("]")),
/// ])
/// .parse(input)
/// };
///
/// let input1: &str = "{\"quoted\"}";
/// let input2: &str = "[\"quoted\"]";
/// assert_eq!(
/// Ok(("", "quoted")),
/// parser.parse(input1)
/// );
/// assert_eq!(Ok(("", "quoted")), parser.parse(input1));
///
/// assert_eq!(
/// Ok(("", "quoted")),
/// parser.parse(input2)
/// );
/// assert_eq!(Ok(("", "quoted")), parser.parse(input2));
/// ```
pub fn alt<'a, P, A, const N: usize>(parsers: [P; N]) -> impl Parser<'a, A>
where
@ -591,20 +578,17 @@ pub fn take<'a>(count: usize) -> impl Parser<'a, &'a str> {
///```rust
/// # use std::str::FromStr;
/// # use melib::parsec::{Parser, delimited, match_literal, map_res, is_a, take_literal};
/// let lit: &str = "{31}\r\nThere is no script by that name\r\n";
/// assert_eq!(
/// take_literal(delimited(
/// match_literal("{"),
/// map_res(is_a(b"0123456789"), |s| usize::from_str(s)),
/// match_literal("}\r\n"),
/// ))
/// .parse(lit),
/// Ok((
/// "\r\n",
/// "There is no script by that name",
/// ))
/// );
///```
/// let lit: &str = "{31}\r\nThere is no script by that name\r\n";
/// assert_eq!(
/// take_literal(delimited(
/// match_literal("{"),
/// map_res(is_a(b"0123456789"), |s| usize::from_str(s)),
/// match_literal("}\r\n"),
/// ))
/// .parse(lit),
/// Ok(("\r\n", "There is no script by that name",))
/// );
/// ```
pub fn take_literal<'a, P>(parser: P) -> impl Parser<'a, &'a str>
where
P: Parser<'a, usize>,
@ -617,9 +601,10 @@ where
#[cfg(test)]
mod test {
use super::*;
use std::collections::HashMap;
use super::*;
#[test]
fn test_parsec() {
#[derive(Debug, PartialEq)]
@ -639,16 +624,16 @@ mod test {
either(
either(
either(
map(parse_bool(), |b| JsonValue::JsonBool(b)),
map(parse_bool(), JsonValue::JsonBool),
map(parse_null(), |()| JsonValue::JsonNull),
),
map(parse_array(), |vec| JsonValue::JsonArray(vec)),
map(parse_array(), JsonValue::JsonArray),
),
map(parse_object(), |obj| JsonValue::JsonObject(obj)),
map(parse_object(), JsonValue::JsonObject),
),
map(parse_number(), |n| JsonValue::JsonNumber(n)),
map(parse_number(), JsonValue::JsonNumber),
),
map(quoted_string(), |s| JsonValue::JsonString(s)),
map(quoted_string(), JsonValue::JsonString),
)
.parse(input)
}

View File

@ -19,14 +19,13 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use crate::parsec::*;
use crate::UnixTimestamp;
use std::borrow::Cow;
use std::convert::TryFrom;
use std::{borrow::Cow, convert::TryFrom};
pub use query_parser::query;
use Query::*;
use crate::{parsec::*, UnixTimestamp};
#[derive(Debug, PartialEq, Clone, Serialize)]
pub enum Query {
Before(UnixTimestamp),
@ -233,9 +232,10 @@ pub mod query_parser {
///
/// # Invocation
/// ```
/// use melib::search::query;
/// use melib::search::Query;
/// use melib::parsec::Parser;
/// use melib::{
/// parsec::Parser,
/// search::{query, Query},
/// };
///
/// let input = "test";
/// let query = query().parse(input);

View File

@ -153,7 +153,8 @@ pub enum ZoneRule {
///time zone in offset format "+hhmm" or "-hhmm". An
///offset of 0 (Zulu) always has a positive sign.
Zone,
/// "weekday" => the day of the week expressed as an integer between "0" and "6". "0" is Sunday, "1" is Monday, etc.
/// "weekday" => the day of the week expressed as an integer between "0"
/// and "6". "0" is Sunday, "1" is Monday, etc.
Weekday,
}
@ -370,9 +371,9 @@ pub mod parser {
),
|(num_s, quant)| {
Ok(match (num_s.parse::<u64>(), quant.to_ascii_lowercase()) {
(Ok(num), 'k') => num * 1000,
(Ok(num), 'm') => num * 1000_000,
(Ok(num), 'g') => num * 1000_000_000,
(Ok(num), 'k') => num * 1_000,
(Ok(num), 'm') => num * 1_000_000,
(Ok(num), 'g') => num * 1_000_000_000,
_ => return Err(num_s),
})
},
@ -483,7 +484,8 @@ pub mod parser {
}
}
// address [COMPARATOR] [ADDRESS-PART] [MATCH-TYPE] <header-list: string-list> <key-list: string-list>
// address [COMPARATOR] [ADDRESS-PART] [MATCH-TYPE] <header-list: string-list>
// <key-list: string-list>
pub fn parse_sieve_address<'a>() -> impl Parser<'a, ConditionRule> {
move |input| {
map(
@ -677,19 +679,12 @@ pub mod parser {
#[cfg(test)]
mod test {
use super::parser::*;
use super::{
parser::*, ActionCommand::*, AddressOperator::*, CharacterOperator::*, ConditionRule::*,
ControlCommand::*, IntegerOperator::*, MatchOperator::*, Rule::*, RuleBlock,
};
use crate::parsec::Parser;
use super::ActionCommand::*;
use super::AddressOperator::*;
use super::CharacterOperator::*;
use super::ConditionRule::*;
use super::ControlCommand::*;
use super::IntegerOperator::*;
use super::MatchOperator::*;
use super::Rule::*;
use super::RuleBlock;
#[test]
fn test_sieve_parse_strings() {
assert_eq!(
@ -705,9 +700,10 @@ mod test {
#[test]
fn test_sieve_parse_conditionals() {
/* Operators that start with : like :matches are unordered and optional, since they have
* defaults. But that means we must handle any order correctly, which is tricky if we use
* an optional parser; for an optional parser both None and Some(_) are valid values.
/* Operators that start with : like :matches are unordered and optional,
* since they have defaults. But that means we must handle any order
* correctly, which is tricky if we use an optional parser; for an
* optional parser both None and Some(_) are valid values.
*/
/* Permutations of two */

View File

@ -24,8 +24,8 @@
//! SMTP client support
//!
//! This module implements a client for the SMTP protocol as specified by [RFC 5321 Simple Mail
//! Transfer Protocol](https://www.rfc-editor.org/rfc/rfc5321).
//! This module implements a client for the SMTP protocol as specified by [RFC
//! 5321 Simple Mail Transfer Protocol](https://www.rfc-editor.org/rfc/rfc5321).
//!
//! The connection and methods are `async` and uses the `smol` runtime.
//!# Example
@ -72,18 +72,18 @@
//! Ok(())
//! ```
use crate::connections::{lookup_ipv4, Connection};
use crate::email::{parser::BytesExt, Address, Envelope};
use crate::error::{Error, Result, ResultIntoError};
use std::{borrow::Cow, convert::TryFrom, net::TcpStream, process::Command};
use futures::io::{AsyncReadExt, AsyncWriteExt};
use native_tls::TlsConnector;
use smallvec::SmallVec;
use smol::unblock;
use smol::Async as AsyncWrapper;
use std::borrow::Cow;
use std::convert::TryFrom;
use std::net::TcpStream;
use std::process::Command;
use smol::{unblock, Async as AsyncWrapper};
use crate::{
connections::{lookup_ipv4, Connection},
email::{parser::BytesExt, Address, Envelope},
error::{Error, Result, ResultIntoError},
};
/// Kind of server security (StartTLS/TLS/None) the client should attempt
#[derive(Debug, Copy, PartialEq, Eq, Clone, Serialize, Deserialize)]
@ -191,11 +191,12 @@ pub struct SmtpExtensionSupport {
/// [RFC 6152: SMTP Service Extension for 8-bit MIME Transport](https://www.rfc-editor.org/rfc/rfc6152)
#[serde(default = "crate::conf::true_val")]
_8bitmime: bool,
/// Essentially, the PRDR extension to SMTP allows (but does not require) an SMTP server to
/// issue multiple responses after a message has been transferred, by mutual consent of the
/// client and server. SMTP clients that support the PRDR extension then use the expanded
/// responses as supplemental data to the responses that were received during the earlier
/// envelope exchange.
/// Essentially, the PRDR extension to SMTP allows (but does not require) an
/// SMTP server to issue multiple responses after a message has been
/// transferred, by mutual consent of the client and server. SMTP
/// clients that support the PRDR extension then use the expanded
/// responses as supplemental data to the responses that were received
/// during the earlier envelope exchange.
#[serde(default = "crate::conf::true_val")]
prdr: bool,
#[serde(default = "crate::conf::true_val")]
@ -284,7 +285,10 @@ impl SmtpConnection {
danger_accept_invalid_certs,
};
} else {
return Err(Error::new("Please specify what SMTP security transport to use explicitly instead of `auto`."));
return Err(Error::new(
"Please specify what SMTP security transport to use explicitly \
instead of `auto`.",
));
}
}
socket.write_all(b"EHLO meli.delivery\r\n").await?;
@ -386,9 +390,11 @@ impl SmtpConnection {
.any(|l| l.starts_with("AUTH"))
{
return Err(Error::new(format!(
"SMTP Server doesn't advertise Authentication support. Server response was: {:?}",
pre_auth_extensions_reply
)).set_kind(crate::error::ErrorKind::Authentication));
"SMTP Server doesn't advertise Authentication support. Server response was: \
{:?}",
pre_auth_extensions_reply
))
.set_kind(crate::error::ErrorKind::Authentication));
}
no_auth_needed =
ret.server_conf.auth == SmtpAuth::None || !ret.server_conf.auth.require_auth();
@ -430,7 +436,7 @@ impl SmtpConnection {
let mut output = unblock(move || {
Command::new("sh")
.args(&["-c", &_command])
.args(["-c", &_command])
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
@ -493,7 +499,7 @@ impl SmtpConnection {
let _token_command = token_command.clone();
let mut output = unblock(move || {
Command::new("sh")
.args(&["-c", &_token_command])
.args(["-c", &_token_command])
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
@ -538,7 +544,7 @@ impl SmtpConnection {
self.server_conf.envelope_from = envelope_from;
}
fn set_extension_support(&mut self, reply: Reply<'_>) {
fn set_extension_support(&mut self, reply: Reply) {
debug_assert_eq!(reply.code, ReplyCode::_250);
self.server_conf.extensions.pipelining &= reply.lines.contains(&"PIPELINING");
self.server_conf.extensions.chunking &= reply.lines.contains(&"CHUNKING");
@ -595,7 +601,10 @@ impl SmtpConnection {
.chain_err_summary(|| "SMTP submission was aborted")?;
let tos = tos.unwrap_or_else(|| envelope.to());
if tos.is_empty() && envelope.cc().is_empty() && envelope.bcc().is_empty() {
return Err(Error::new("SMTP submission was aborted because there was no e-mail address found in the To: header field. Consider adding recipients."));
return Err(Error::new(
"SMTP submission was aborted because there was no e-mail address found in the To: \
header field. Consider adding recipients.",
));
}
let mut current_command: SmallVec<[&[u8]; 16]> = SmallVec::new();
//first step in the procedure is the MAIL command.
@ -605,9 +614,17 @@ impl SmtpConnection {
current_command.push(envelope_from.trim().as_bytes());
} else {
if envelope.from().is_empty() {
return Err(Error::new("SMTP submission was aborted because there was no e-mail address found in the From: header field. Consider adding a valid value or setting `envelope_from` in SMTP client settings"));
return Err(Error::new(
"SMTP submission was aborted because there was no e-mail address found in the \
From: header field. Consider adding a valid value or setting `envelope_from` \
in SMTP client settings",
));
} else if envelope.from().len() != 1 {
return Err(Error::new("SMTP submission was aborted because there was more than one e-mail address found in the From: header field. Consider setting `envelope_from` in SMTP client settings"));
return Err(Error::new(
"SMTP submission was aborted because there was more than one e-mail address \
found in the From: header field. Consider setting `envelope_from` in SMTP \
client settings",
));
}
current_command.push(envelope.from()[0].address_spec_raw().trim());
}
@ -628,12 +645,13 @@ impl SmtpConnection {
} else {
pipelining_queue.push(Some((ReplyCode::_250, &[])));
}
//The second step in the procedure is the RCPT command. This step of the procedure can
//be repeated any number of times. If accepted, the SMTP server returns a "250 OK"
//reply. If the mailbox specification is not acceptable for some reason, the server MUST
//return a reply indicating whether the failure is permanent (i.e., will occur again if
//the client tries to send the same address again) or temporary (i.e., the address might
//be accepted if the client tries again later).
//The second step in the procedure is the RCPT command. This step of the
// procedure can be repeated any number of times. If accepted, the SMTP
// server returns a "250 OK" reply. If the mailbox specification is not
// acceptable for some reason, the server MUST return a reply indicating
// whether the failure is permanent (i.e., will occur again if
// the client tries to send the same address again) or temporary (i.e., the
// address might be accepted if the client tries again later).
for addr in tos
.iter()
.chain(envelope.cc().iter())
@ -651,7 +669,8 @@ impl SmtpConnection {
self.send_command(&current_command).await?;
//RCPT TO:<forward-path> [ SP <rcpt-parameters> ] <CRLF>
//If accepted, the SMTP server returns a "250 OK" reply and stores the forward-path.
//If accepted, the SMTP server returns a "250 OK" reply and stores the
// forward-path.
if !self.server_conf.extensions.pipelining {
self.read_lines(&mut res, Some((ReplyCode::_250, &[])))
.await?;
@ -660,9 +679,10 @@ impl SmtpConnection {
}
}
//Since it has been a common source of errors, it is worth noting that spaces are not
//permitted on either side of the colon following FROM in the MAIL command or TO in the
//RCPT command. The syntax is exactly as given above.
//Since it has been a common source of errors, it is worth noting that spaces
// are not permitted on either side of the colon following FROM in the
// MAIL command or TO in the RCPT command. The syntax is exactly as
// given above.
if self.server_conf.extensions.binarymime {
let mail_length = format!("{}", mail.as_bytes().len());
@ -674,13 +694,14 @@ impl SmtpConnection {
//(or some alternative specified in a service extension).
//DATA <CRLF>
self.send_command(&[b"DATA"]).await?;
//Client SMTP implementations that employ pipelining MUST check ALL statuses associated
//with each command in a group. For example, if none of the RCPT TO recipient addresses
//were accepted the client must then check the response to the DATA command -- the client
//cannot assume that the DATA command will be rejected just because none of the RCPT TO
//commands worked. If the DATA command was properly rejected the client SMTP can just
//issue RSET, but if the DATA command was accepted the client SMTP should send a single
//dot.
//Client SMTP implementations that employ pipelining MUST check ALL statuses
// associated with each command in a group. For example, if none of
// the RCPT TO recipient addresses were accepted the client must
// then check the response to the DATA command -- the client
// cannot assume that the DATA command will be rejected just because none of the
// RCPT TO commands worked. If the DATA command was properly
// rejected the client SMTP can just issue RSET, but if the DATA
// command was accepted the client SMTP should send a single dot.
let mut _all_error = self.server_conf.extensions.pipelining;
let mut _any_error = false;
let mut ignore_mailfrom = true;
@ -694,15 +715,17 @@ impl SmtpConnection {
pipelining_results.push(reply.into());
}
//If accepted, the SMTP server returns a 354 Intermediate reply and considers all
//succeeding lines up to but not including the end of mail data indicator to be the
//message text. When the end of text is successfully received and stored, the
//SMTP-receiver sends a "250 OK" reply.
//If accepted, the SMTP server returns a 354 Intermediate reply and considers
// all succeeding lines up to but not including the end of mail data
// indicator to be the message text. When the end of text is
// successfully received and stored, the SMTP-receiver sends a "250
// OK" reply.
self.read_lines(&mut res, Some((ReplyCode::_354, &[])))
.await?;
//Before sending a line of mail text, the SMTP client checks the first character of the
//line.If it is a period, one additional period is inserted at the beginning of the line.
//Before sending a line of mail text, the SMTP client checks the first
// character of the line.If it is a period, one additional period is
// inserted at the beginning of the line.
for line in mail.lines() {
if line.starts_with('.') {
self.stream.write_all(b".").await?;
@ -715,15 +738,16 @@ impl SmtpConnection {
self.stream.write_all(b".\r\n").await?;
}
//The mail data are terminated by a line containing only a period, that is, the character
//sequence "<CRLF>.<CRLF>", where the first <CRLF> is actually the terminator of the
//previous line (see Section 4.5.2). This is the end of mail data indication.
//The mail data are terminated by a line containing only a period, that is, the
// character sequence "<CRLF>.<CRLF>", where the first <CRLF> is
// actually the terminator of the previous line (see Section 4.5.2).
// This is the end of mail data indication.
self.stream.write_all(b".\r\n").await?;
}
//The end of mail data indicator also confirms the mail transaction and tells the SMTP
//server to now process the stored recipients and mail data. If accepted, the SMTP
//server returns a "250 OK" reply.
//The end of mail data indicator also confirms the mail transaction and tells
// the SMTP server to now process the stored recipients and mail data.
// If accepted, the SMTP server returns a "250 OK" reply.
let reply_code = self
.read_lines(
&mut res,
@ -760,7 +784,9 @@ pub type ExpectedReplyCode = Option<(ReplyCode, &'static [ReplyCode])>;
pub enum ReplyCode {
/// System status, or system help reply
_211,
/// Help message (Information on how to use the receiver or the meaning of a particular non-standard command; this reply is useful only to the human user)
/// Help message (Information on how to use the receiver or the meaning of a
/// particular non-standard command; this reply is useful only to the human
/// user)
_214,
/// <domain> Service ready
_220,
@ -772,7 +798,8 @@ pub enum ReplyCode {
_250,
/// User not local; will forward to <forward-path> (See Section 3.4)
_251,
/// Cannot VRFY user, but will accept message and attempt delivery (See Section 3.5.3)
/// Cannot VRFY user, but will accept message and attempt delivery (See
/// Section 3.5.3)
_252,
/// rfc4954 AUTH continuation request
_334,
@ -780,9 +807,11 @@ pub enum ReplyCode {
_353,
/// Start mail input; end with <CRLF>.<CRLF>
_354,
/// <domain> Service not available, closing transmission channel (This may be a reply to any command if the service knows it must shut down)
/// <domain> Service not available, closing transmission channel (This may
/// be a reply to any command if the service knows it must shut down)
_421,
/// Requested mail action not taken: mailbox unavailable (e.g., mailbox busy or temporarily blocked for policy reasons)
/// Requested mail action not taken: mailbox unavailable (e.g., mailbox busy
/// or temporarily blocked for policy reasons)
_450,
/// Requested action aborted: local error in processing
_451,
@ -790,7 +819,8 @@ pub enum ReplyCode {
_452,
/// Server unable to accommodate parameters
_455,
/// Syntax error, command unrecognized (This may include errors such as command line too long)
/// Syntax error, command unrecognized (This may include errors such as
/// command line too long)
_500,
/// Syntax error in parameters or arguments
_501,
@ -802,15 +832,18 @@ pub enum ReplyCode {
_504,
/// Authentication failed
_535,
/// Requested action not taken: mailbox unavailable (e.g., mailbox not found, no access, or command rejected for policy reasons)
/// Requested action not taken: mailbox unavailable (e.g., mailbox not
/// found, no access, or command rejected for policy reasons)
_550,
/// User not local; please try <forward-path> (See Section 3.4)
_551,
/// Requested mail action aborted: exceeded storage allocation
_552,
/// Requested action not taken: mailbox name not allowed (e.g., mailbox syntax incorrect)
/// Requested action not taken: mailbox name not allowed (e.g., mailbox
/// syntax incorrect)
_553,
/// Transaction failed (Or, in the case of a connection-opening response, "No SMTP service here")
/// Transaction failed (Or, in the case of a connection-opening response,
/// "No SMTP service here")
_554,
/// MAIL FROM/RCPT TO parameters not recognized or not implemented
_555,
@ -844,10 +877,16 @@ impl ReplyCode {
_503 => "Bad sequence of commands",
_504 => "Command parameter not implemented",
_535 => "Authentication failed",
_550 => "Requested action not taken: mailbox unavailable (e.g., mailbox not found, no access, or command rejected for policy reasons)",
_550 => {
"Requested action not taken: mailbox unavailable (e.g., mailbox not found, no \
access, or command rejected for policy reasons)"
}
_551 => "User not local",
_552 => "Requested mail action aborted: exceeded storage allocation",
_553 => "Requested action not taken: mailbox name not allowed (e.g., mailbox syntax incorrect)",
_553 => {
"Requested action not taken: mailbox name not allowed (e.g., mailbox syntax \
incorrect)"
}
_554 => "Transaction failed",
_555 => "MAIL FROM/RCPT TO parameters not recognized or not implemented",
_530 => "Must issue a STARTTLS command first",
@ -927,19 +966,19 @@ pub struct Reply<'s> {
pub lines: SmallVec<[&'s str; 16]>,
}
impl<'s> Into<Result<ReplyCode>> for Reply<'s> {
fn into(self: Reply<'s>) -> Result<ReplyCode> {
if self.code.is_err() {
Err(Error::new(self.lines.join("\n")).set_summary(self.code.as_str()))
impl<'s> From<Reply<'s>> for Result<ReplyCode> {
fn from(val: Reply<'s>) -> Self {
if val.code.is_err() {
Err(Error::new(val.lines.join("\n")).set_summary(val.code.as_str()))
} else {
Ok(self.code)
Ok(val.code)
}
}
}
impl<'s> Reply<'s> {
/// `s` must be raw SMTP output i.e each line must start with 3 digit reply code, a space
/// or '-' and end with '\r\n'
/// `s` must be raw SMTP output i.e each line must start with 3 digit reply
/// code, a space or '-' and end with '\r\n'
pub fn new(s: &'s str, code: ReplyCode) -> Self {
let lines: SmallVec<_> = s.lines().map(|l| &l[4..l.len()]).collect();
Reply { lines, code }
@ -959,7 +998,9 @@ async fn read_lines<'r>(
let mut returned_code: Option<ReplyCode> = None;
'read_loop: loop {
while let Some(pos) = ret[last_line_idx..].find("\r\n") {
// "Formally, a reply is defined to be the sequence: a three-digit code, <SP>, one line of text, and <CRLF>, or a multiline reply (as defined in the same section)."
// "Formally, a reply is defined to be the sequence: a three-digit code, <SP>,
// one line of text, and <CRLF>, or a multiline reply (as defined in the same
// section)."
if ret[last_line_idx..].len() < 4
|| !ret[last_line_idx..]
.chars()
@ -1022,12 +1063,15 @@ async fn read_lines<'r>(
#[cfg(test)]
mod test {
use super::*;
use std::net::IpAddr; //, Ipv4Addr, Ipv6Addr};
use std::{
sync::{Arc, Mutex},
thread,
};
use mailin_embedded::{Handler, Response, Server, SslConfig};
use std::net::IpAddr; //, Ipv4Addr, Ipv6Addr};
use std::sync::{Arc, Mutex};
use std::thread;
use super::*;
const ADDRESS: &str = "127.0.0.1:8825";
#[derive(Debug, Clone)]
@ -1229,7 +1273,7 @@ mod test {
futures::executor::block_on(SmtpConnection::new_connection(smtp_server_conf)).unwrap();
futures::executor::block_on(connection.mail_transaction(
input_str,
/*tos*/
/* tos */
Some(&[
Address::try_from("foo-chat@example.com").unwrap(),
Address::try_from("webmaster@example.com").unwrap(),

View File

@ -19,10 +19,12 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use crate::{error::*, logging::log, Envelope};
use std::path::PathBuf;
use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput};
pub use rusqlite::{self, params, Connection};
use std::path::PathBuf;
use crate::{error::*, logging::log, Envelope};
#[derive(Copy, Clone, Debug)]
pub struct DatabaseDescription {
@ -90,8 +92,9 @@ pub fn open_or_create_db(
);
if second_try {
return Err(Error::new(format!(
"Database version mismatch, is {} but expected {}. Could not recreate database.",
version, description.version
"Database version mismatch, is {} but expected {}. Could not recreate \
database.",
version, description.version
)));
}
reset_db(description, identifier)?;
@ -100,7 +103,7 @@ pub fn open_or_create_db(
}
if version == 0 {
conn.pragma_update(None, "user_version", &description.version)?;
conn.pragma_update(None, "user_version", description.version)?;
}
if let Some(s) = description.init_script {
conn.execute_batch(s)

File diff suppressed because it is too large Load Diff

View File

@ -20,17 +20,18 @@
*/
extern crate unicode_segmentation;
use self::unicode_segmentation::UnicodeSegmentation;
use super::grapheme_clusters::TextProcessing;
use super::tables::LINE_BREAK_RULES;
use super::types::LineBreakClass;
use super::types::Reflow;
use core::cmp::Ordering;
use core::iter::Peekable;
use core::str::FromStr;
use core::{cmp::Ordering, iter::Peekable, str::FromStr};
use std::collections::VecDeque;
use LineBreakClass::*;
use self::unicode_segmentation::UnicodeSegmentation;
use super::{
grapheme_clusters::TextProcessing,
tables::LINE_BREAK_RULES,
types::{LineBreakClass, Reflow},
};
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum LineBreakCandidate {
MandatoryBreak,
@ -138,14 +139,32 @@ impl EvenAfterSpaces for str {
/// Returns positions where breaks can happen
/// Examples:
/// ```
/// use melib::text_processing::{self, LineBreakCandidate::{self, *}};
/// use melib::text_processing::line_break::LineBreakCandidateIter;
/// use melib::text_processing::{
/// self,
/// line_break::LineBreakCandidateIter,
/// LineBreakCandidate::{self, *},
/// };
///
/// assert!(LineBreakCandidateIter::new("").collect::<Vec<(usize, LineBreakCandidate)>>().is_empty());
/// assert_eq!(&[(7, BreakAllowed), (12, MandatoryBreak)],
/// LineBreakCandidateIter::new("Sample Text.").collect::<Vec<(usize, LineBreakCandidate)>>().as_slice());
/// assert_eq!(&[(3, MandatoryBreak), (7, MandatoryBreak), (10, BreakAllowed), (17, MandatoryBreak)],
/// LineBreakCandidateIter::new("Sa\nmp\r\nle T(e)xt.").collect::<Vec<(usize, LineBreakCandidate)>>().as_slice());
/// assert!(LineBreakCandidateIter::new("")
/// .collect::<Vec<(usize, LineBreakCandidate)>>()
/// .is_empty());
/// assert_eq!(
/// &[(7, BreakAllowed), (12, MandatoryBreak)],
/// LineBreakCandidateIter::new("Sample Text.")
/// .collect::<Vec<(usize, LineBreakCandidate)>>()
/// .as_slice()
/// );
/// assert_eq!(
/// &[
/// (3, MandatoryBreak),
/// (7, MandatoryBreak),
/// (10, BreakAllowed),
/// (17, MandatoryBreak)
/// ],
/// LineBreakCandidateIter::new("Sa\nmp\r\nle T(e)xt.")
/// .collect::<Vec<(usize, LineBreakCandidate)>>()
/// .as_slice()
/// );
/// ```
impl<'a> Iterator for LineBreakCandidateIter<'a> {
type Item = (usize, LineBreakCandidate);
@ -190,13 +209,13 @@ impl<'a> Iterator for LineBreakCandidateIter<'a> {
*reg_ind_streak = 0;
}
/* LB1 Assign a line breaking class to each code point of the input. Resolve AI, CB, CJ,
* SA, SG, and XX into other line breaking classes depending on criteria outside the scope
* of this algorithm.
/* LB1 Assign a line breaking class to each code point of the input. Resolve
* AI, CB, CJ, SA, SG, and XX into other line breaking classes
* depending on criteria outside the scope of this algorithm.
*
* In the absence of such criteria all characters with a specific combination of original
* class and General_Category property value are resolved as follows:
* Resolved Original General_Category
* In the absence of such criteria all characters with a specific combination
* of original class and General_Category property value are
* resolved as follows: Resolved Original General_Category
* AL AI, SG, XX Any
* CM SA Only Mn or Mc
* AL SA Any except Mn and Mc
@ -245,7 +264,8 @@ impl<'a> Iterator for LineBreakCandidateIter<'a> {
continue;
}
WJ => {
/*: LB11 Do not break before or after Word joiner and related characters.*/
/* : LB11 Do not break before or after Word joiner and related
* characters. */
*pos += grapheme.len();
continue;
}
@ -266,8 +286,8 @@ impl<'a> Iterator for LineBreakCandidateIter<'a> {
}
match class {
ZW => {
// LB8 Break before any character following a zero-width space, even if one or more
// spaces intervene
// LB8 Break before any character following a zero-width space, even if one or
// more spaces intervene
// ZW SP* ÷
*pos += grapheme.len();
while next_grapheme_class!((next_char is SP)) {
@ -286,9 +306,9 @@ impl<'a> Iterator for LineBreakCandidateIter<'a> {
}
CM => {
// LB9 Do not break a combining character sequence; treat it as if it has the line
// breaking class of the base character in all of the following rules. Treat ZWJ as
// if it were CM.
// LB9 Do not break a combining character sequence; treat it as if it has the
// line breaking class of the base character in all of the
// following rules. Treat ZWJ as if it were CM.
// Treat X (CM | ZWJ)* as if it were X.
// where X is any line break class except BK, CR, LF, NL, SP, or ZW.
@ -296,7 +316,7 @@ impl<'a> Iterator for LineBreakCandidateIter<'a> {
continue;
}
WJ => {
/*: LB11 Do not break before or after Word joiner and related characters.*/
/* : LB11 Do not break before or after Word joiner and related characters. */
*pos += grapheme.len();
/* Get next grapheme */
if next_grapheme_class!(iter, grapheme).is_some() {
@ -305,7 +325,8 @@ impl<'a> Iterator for LineBreakCandidateIter<'a> {
continue;
}
GL => {
/*LB12 Non-breaking characters: LB12 Do not break after NBSP and related characters.*/
/* LB12 Non-breaking characters: LB12 Do not break after NBSP and related
* characters. */
*pos += grapheme.len();
continue;
}
@ -315,8 +336,8 @@ impl<'a> Iterator for LineBreakCandidateIter<'a> {
let next_class = get_class!(next_grapheme);
match next_class {
GL if ![SP, BA, HY].contains(&class) => {
/* LB12a Do not break before NBSP and related characters, except after spaces and
* hyphens. [^SP BA HY] × GL
/* LB12a Do not break before NBSP and related characters, except after
* spaces and hyphens. [^SP BA HY] × GL
* Also LB12 Do not break after NBSP and related characters */
*pos += grapheme.len();
continue;
@ -384,8 +405,8 @@ impl<'a> Iterator for LineBreakCandidateIter<'a> {
if !text[idx + grapheme.len()..].even_after_spaces().is_empty()
&& get_class!(text[idx + grapheme.len()..].even_after_spaces()) == NS =>
{
/* LB16 Do not break between closing punctuation and a nonstarter (lb=NS), even with
* intervening spaces.
/* LB16 Do not break between closing punctuation and a nonstarter (lb=NS),
* even with intervening spaces.
* (CL | CP) SP* × NS */
*pos += grapheme.len();
while Some(SP) == next_grapheme_class!(iter, grapheme) {
@ -397,7 +418,7 @@ impl<'a> Iterator for LineBreakCandidateIter<'a> {
&& get_class!(text[idx + grapheme.len()..].even_after_spaces()) == B2 =>
{
/* LB17 Do not break within ‘——’, even with intervening spaces.
* B2 SP* × B2*/
* B2 SP* × B2 */
*pos += grapheme.len();
continue;
}
@ -434,8 +455,9 @@ impl<'a> Iterator for LineBreakCandidateIter<'a> {
set_last_break!(*last_break, ret);
return Some((ret, BreakAllowed));
}
/* LB21 Do not break before hyphen-minus, other hyphens, fixed-width spaces, small
* kana, and other non-starters, or after acute accents. × BA, × HY, × NS, BB × */
/* LB21 Do not break before hyphen-minus, other hyphens, fixed-width spaces,
* small kana, and other non-starters, or after acute accents. ×
* BA, × HY, × NS, BB × */
BB if !*break_now => {
*pos += grapheme.len();
continue;
@ -447,8 +469,9 @@ impl<'a> Iterator for LineBreakCandidateIter<'a> {
let next_class = get_class!(next_grapheme);
match next_class {
BA | HY | NS => {
/* LB21 Do not break before hyphen-minus, other hyphens, fixed-width spaces, small
* kana, and other non-starters, or after acute accents. × BA, × HY, × NS, BB × */
/* LB21 Do not break before hyphen-minus, other hyphens, fixed-width
* spaces, small kana, and other non-starters, or
* after acute accents. × BA, × HY, × NS, BB × */
*pos += grapheme.len();
//*pos += next_grapheme.len();
continue;
@ -485,7 +508,7 @@ impl<'a> Iterator for LineBreakCandidateIter<'a> {
self.iter.next();
continue;
}
/* EX × IN */
/* EX × IN */
EX if next_grapheme_class!((next_char is IN)) => {
let (idx, next_grapheme) = next_char.unwrap();
*pos = idx + next_grapheme.len();
@ -497,21 +520,21 @@ impl<'a> Iterator for LineBreakCandidateIter<'a> {
*pos += grapheme.len();
continue;
}
/* (ID | EB | EM) × IN */
/* (ID | EB | EM) × IN */
ID | EB | EM if next_grapheme_class!((next_char is IN)) => {
let (idx, next_grapheme) = next_char.unwrap();
*pos = idx + next_grapheme.len();
self.iter.next();
continue;
}
/* IN × IN */
/* IN × IN */
IN if next_grapheme_class!((next_char is IN)) => {
let (idx, next_grapheme) = next_char.unwrap();
*pos = idx + next_grapheme.len();
self.iter.next();
continue;
}
/* NU × IN */
/* NU × IN */
NU if next_grapheme_class!((next_char is IN)) => {
let (idx, next_grapheme) = next_char.unwrap();
*pos = idx + next_grapheme.len();
@ -533,8 +556,8 @@ impl<'a> Iterator for LineBreakCandidateIter<'a> {
self.iter.next();
continue;
}
/* LB23a Do not break between numeric prefixes and ideographs, or between ideographs
* and numeric postfixes.
/* LB23a Do not break between numeric prefixes and ideographs, or between
* ideographs and numeric postfixes.
* PR × (ID | EB | EM) */
PR if next_grapheme_class!((next_char is ID, EB, EM)) => {
let (idx, next_grapheme) = next_char.unwrap();
@ -558,7 +581,7 @@ impl<'a> Iterator for LineBreakCandidateIter<'a> {
self.iter.next();
continue;
}
/*(AL | HL) × (PR | PO) */
/* (AL | HL) × (PR | PO) */
AL | HL if next_grapheme_class!((next_char is PR, PO)) => {
let (idx, next_grapheme) = next_char.unwrap();
*pos = idx + next_grapheme.len();
@ -749,9 +772,9 @@ impl<'a> Iterator for LineBreakCandidateIter<'a> {
continue;
}
RI => {
/* LB30a Break between two regional indicator symbols if and only if there are an
* even number of regional indicators preceding the position of the break.
* sot (RI RI)* RI × RI
/* LB30a Break between two regional indicator symbols if and only if there
* are an even number of regional indicators preceding
* the position of the break. sot (RI RI)* RI × RI
* [^RI] (RI RI)* RI × RI */
*reg_ind_streak += 1;
*pos += grapheme.len();
@ -852,8 +875,7 @@ mod tests {
pub use alg::linear;
mod alg {
use super::super::grapheme_clusters::TextProcessing;
use super::super::*;
use super::super::{grapheme_clusters::TextProcessing, *};
fn cost(i: usize, j: usize, width: usize, minima: &[usize], offsets: &[usize]) -> usize {
let w = offsets[j] + j - offsets[i] - i - 1;
if w > width {
@ -1060,7 +1082,7 @@ pub fn split_lines_reflow(text: &str, reflow: Reflow, width: Option<usize>) -> V
} else if prev_quote_depth == quote_depth {
/* This becomes part of the paragraph we're in */
} else {
/*Malformed line, different quote depths can't be in the same paragraph. */
/* Malformed line, different quote depths can't be in the same paragraph. */
let paragraph = &text[paragraph_start..prev_index];
reflow_helper(&mut ret, paragraph, prev_quote_depth, in_paragraph, width);
@ -1071,7 +1093,7 @@ pub fn split_lines_reflow(text: &str, reflow: Reflow, width: Option<usize>) -> V
let paragraph = &text[paragraph_start..*i];
reflow_helper(&mut ret, paragraph, quote_depth, in_paragraph, width);
} else {
/*Malformed line, different quote depths can't be in the same paragraph. */
/* Malformed line, different quote depths can't be in the same paragraph. */
let paragraph = &text[paragraph_start..prev_index];
reflow_helper(&mut ret, paragraph, prev_quote_depth, in_paragraph, width);
let paragraph = &text[prev_index..*i];
@ -1248,12 +1270,13 @@ easy to take MORE than nothing.'"#;
}
mod segment_tree {
/*! Simple segment tree implementation for maximum in range queries. This is useful if given an
* array of numbers you want to get the maximum value inside an interval quickly.
/*! Simple segment tree implementation for maximum in range queries. This
* is useful if given an array of numbers you want to get the
* maximum value inside an interval quickly.
*/
use std::{convert::TryFrom, iter::FromIterator};
use smallvec::SmallVec;
use std::convert::TryFrom;
use std::iter::FromIterator;
#[derive(Default, Debug, Clone)]
pub(super) struct SegmentTree {
@ -1329,8 +1352,9 @@ mod segment_tree {
}
}
/// A lazy stateful iterator for line breaking text. Useful for very long text where you don't want
/// to linebreak it completely before user requests specific lines.
/// A lazy stateful iterator for line breaking text. Useful for very long text
/// where you don't want to linebreak it completely before user requests
/// specific lines.
#[derive(Debug, Clone)]
pub struct LineBreakText {
text: String,
@ -1511,7 +1535,8 @@ impl Iterator for LineBreakText {
} else if prev_quote_depth == quote_depth {
/* This becomes part of the paragraph we're in */
} else {
/*Malformed line, different quote depths can't be in the same paragraph. */
/* Malformed line, different quote depths can't be in the same
* paragraph. */
let paragraph_s = &self.text[paragraph_start..prev_index];
reflow_helper2(
&mut paragraph,
@ -1534,7 +1559,8 @@ impl Iterator for LineBreakText {
self.width,
);
} else {
/*Malformed line, different quote depths can't be in the same paragraph. */
/* Malformed line, different quote depths can't be in the same
* paragraph. */
let paragraph_s = &self.text[paragraph_start..prev_index];
reflow_helper2(
&mut paragraph,

View File

@ -19,10 +19,10 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use super::TextProcessing;
use smallvec::SmallVec;
use super::TextProcessing;
pub trait KMP {
fn kmp_search(&self, pattern: &str) -> SmallVec<[usize; 256]>;
fn kmp_table(graphemes: &[&str]) -> SmallVec<[i32; 256]> {

View File

@ -123,15 +123,10 @@ impl From<&str> for LineBreakClass {
}
}
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
#[derive(PartialEq, Eq, Debug, Copy, Clone, Default)]
pub enum Reflow {
No,
All,
#[default]
FormatFlowed,
}
impl Default for Reflow {
fn default() -> Self {
Reflow::FormatFlowed
}
}

View File

@ -48,7 +48,10 @@ pub struct CodePointsIterator<'a> {
}
/*
* UTF-8 uses a system of binary prefixes, in which the high bits of each byte mark whether its a single byte, the beginning of a multi-byte sequence, or a continuation byte; the remaining bits, concatenated, give the code point index. This table shows how it works:
* UTF-8 uses a system of binary prefixes, in which the high bits of each
* byte mark whether its a single byte, the beginning of a multi-byte
* sequence, or a continuation byte; the remaining bits, concatenated, give
* the code point index. This table shows how it works:
*
* UTF-8 (binary) Code point (binary) Range
* 0xxxxxxx xxxxxxx U+0000U+007F

View File

@ -25,36 +25,38 @@
* This module implements Jamie Zawinski's [threading algorithm](https://www.jwz.org/doc/threading.html). Quoted comments (/* " .. " */) are
* taken almost verbatim from the algorithm.
*
* The entry point of this module is the `Threads` struct and its `new` method. It contains
* `ThreadNodes` which are the nodes in the thread trees that might have messages associated with
* them. The root nodes (first messages in each thread) are stored in `root_set` and `tree`
* vectors. `Threads` has inner mutability since we need to sort without the user having mutable
* ownership.
* The entry point of this module is the `Threads` struct and its `new`
* method. It contains `ThreadNodes` which are the nodes in the thread trees
* that might have messages associated with them. The root nodes (first
* messages in each thread) are stored in `root_set` and `tree`
* vectors. `Threads` has inner mutability since we need to sort without the
* user having mutable ownership.
*/
use crate::datetime::UnixTimestamp;
use crate::email::address::StrBuild;
use crate::email::parser::BytesExt;
use crate::email::*;
use crate::{
datetime::UnixTimestamp,
email::{address::StrBuild, parser::BytesExt, *},
};
mod iterators;
use std::{
cmp::Ordering,
collections::{HashMap, HashSet},
fmt,
iter::FromIterator,
ops::Index,
result::Result as StdResult,
str::FromStr,
string::ToString,
sync::{Arc, RwLock},
};
pub use iterators::*;
use smallvec::SmallVec;
use uuid::Uuid;
#[cfg(feature = "unicode_algorithms")]
use crate::text_processing::grapheme_clusters::*;
use uuid::Uuid;
use std::cmp::Ordering;
use std::collections::{HashMap, HashSet};
use std::fmt;
use std::iter::FromIterator;
use std::ops::Index;
use std::result::Result as StdResult;
use std::str::FromStr;
use std::string::ToString;
use std::sync::{Arc, RwLock};
use smallvec::SmallVec;
type Envelopes = Arc<RwLock<HashMap<EnvelopeHash, Envelope>>>;
@ -120,24 +122,35 @@ macro_rules! make {
let old_group_hash = $threads.find_group($threads.thread_nodes[&$c].group);
let parent_group_hash = $threads.find_group($threads.thread_nodes[&$p].group);
if old_group_hash != parent_group_hash {
if let Some(old_env_hashes) = $threads.thread_to_envelope.get(&old_group_hash).cloned() {
if let Some(old_env_hashes) = $threads.thread_to_envelope.get(&old_group_hash).cloned()
{
for &env_hash in &old_env_hashes {
*$threads.envelope_to_thread.entry(env_hash).or_default() = parent_group_hash;
}
$threads.thread_to_envelope.entry(parent_group_hash).or_default().extend(old_env_hashes.into_iter());
$threads
.thread_to_envelope
.entry(parent_group_hash)
.or_default()
.extend(old_env_hashes.into_iter());
}
let prev_parent = remove_from_parent!(&mut $threads.thread_nodes, $c);
if !($threads.thread_nodes[&$p]).children.contains(&$c) {
/* Pruned nodes keep their children in case they show up in a later merge, so do not panic
* if children exists */
$threads.thread_nodes.entry($p).and_modify(|e| e.children.push($c));
/* Pruned nodes keep their children in case they show up in a later merge, so
* do not panic if children exists */
$threads
.thread_nodes
.entry($p)
.and_modify(|e| e.children.push($c));
}
$threads.thread_nodes.entry($c).and_modify(|e| {
e.parent = Some($p);
});
let old_group = std::mem::replace($threads.groups.entry(old_group_hash).or_default(), ThreadGroup::Node {
parent: Arc::new(RwLock::new(parent_group_hash)),
});
let old_group = std::mem::replace(
$threads.groups.entry(old_group_hash).or_default(),
ThreadGroup::Node {
parent: Arc::new(RwLock::new(parent_group_hash)),
},
);
$threads.thread_nodes.entry($c).and_modify(|e| {
e.group = parent_group_hash;
});
@ -147,21 +160,24 @@ macro_rules! make {
{
let parent_group = $threads.thread_ref_mut(parent_group_hash);
match (parent_group, old_group) {
(Thread {
ref mut date,
ref mut len,
ref mut unseen,
ref mut snoozed,
ref mut attachments,
..
}, ThreadGroup::Root(Thread {
date: old_date,
len: old_len,
unseen: old_unseen,
snoozed: old_snoozed,
attachments: old_attachments,
..
})) => {
(
Thread {
ref mut date,
ref mut len,
ref mut unseen,
ref mut snoozed,
ref mut attachments,
..
},
ThreadGroup::Root(Thread {
date: old_date,
len: old_len,
unseen: old_unseen,
snoozed: old_snoozed,
attachments: old_attachments,
..
}),
) => {
*date = std::cmp::max(old_date, *date);
*len += old_len;
*unseen += old_unseen;
@ -169,13 +185,13 @@ macro_rules! make {
*snoozed |= old_snoozed;
}
_ => unreachable!(),
}
}
}
prev_parent
} else {
None
}
}};
}};
}
/// Strip common prefixes from subjects
@ -185,9 +201,15 @@ macro_rules! make {
/// use melib::thread::SubjectPrefix;
///
/// let mut subject = "Re: RE: Res: Re: Res: Subject";
/// assert_eq!(subject.strip_prefixes_from_list(<&str>::USUAL_PREFIXES, None), &"Subject");
/// assert_eq!(
/// subject.strip_prefixes_from_list(<&str>::USUAL_PREFIXES, None),
/// &"Subject"
/// );
/// let mut subject = "Re: RE: Res: Re: Res: Subject";
/// assert_eq!(subject.strip_prefixes_from_list(<&str>::USUAL_PREFIXES, Some(1)), &"RE: Res: Re: Res: Subject");
/// assert_eq!(
/// subject.strip_prefixes_from_list(<&str>::USUAL_PREFIXES, Some(1)),
/// &"RE: Res: Re: Res: Subject"
/// );
/// ```
pub trait SubjectPrefix {
const USUAL_PREFIXES: &'static [&'static str] = &[
@ -199,7 +221,7 @@ pub trait SubjectPrefix {
"Fw:",
/* taken from
* https://en.wikipedia.org/wiki/List_of_email_subject_abbreviations#Abbreviations_in_other_languages
* */
*/
"回复:",
"回覆:",
// Dutch (Antwoord)
@ -919,8 +941,9 @@ impl Threads {
.unwrap()
.set_thread(thread_hash);
/* If thread node currently has a message from a foreign mailbox and env_hash is
* from current mailbox we want to update it, otherwise return */
/* If thread node currently has a message from a foreign mailbox and env_hash
* is from current mailbox we want to update it, otherwise
* return */
if node.other_mailbox || other_mailbox {
return false;
}

View File

@ -19,12 +19,14 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use super::{ThreadNode, ThreadNodeHash};
use smallvec::SmallVec;
use std::collections::HashMap;
/* `ThreadsIterator` returns messages according to the sorted order. For example, for the following
* threads:
use smallvec::SmallVec;
use super::{ThreadNode, ThreadNodeHash};
/* `ThreadsIterator` returns messages according to the sorted order. For
* example, for the following threads:
*
* ```
* A_
@ -82,8 +84,8 @@ impl<'a> Iterator for ThreadsGroupIterator<'a> {
}
}
}
/* `ThreadIterator` returns messages of a specific thread according to the sorted order. For example, for the following
* thread:
/* `ThreadIterator` returns messages of a specific thread according to the
* sorted order. For example, for the following thread:
*
* ```
* A_

View File

@ -1 +1,7 @@
edition = "2018"
format_generated_files = false
format_code_in_doc_comments = true
format_strings = true
imports_granularity = "Crate"
group_imports = "StdExternalCrate"
wrap_comments = true

View File

@ -67,7 +67,9 @@ pub enum SubCommand {
PrintLoadedThemes,
/// edit configuration files in `$EDITOR`/`$VISUAL`.
EditConfig,
/// create a sample configuration file with available configuration options. If PATH is not specified, meli will try to create it in $XDG_CONFIG_HOME/meli/config.toml
/// create a sample configuration file with available configuration options.
/// If PATH is not specified, meli will try to create it in
/// $XDG_CONFIG_HOME/meli/config.toml
#[structopt(display_order = 1)]
CreateConfig {
#[structopt(value_name = "NEW_CONFIG_PATH", parse(from_os_str))]

View File

@ -20,35 +20,42 @@
*/
/*! A parser module for user commands passed through Command mode.
*/
use crate::melib::parser::BytesExt;
use melib::nom::{
self,
branch::alt,
bytes::complete::{is_a, is_not, tag, take_until},
character::complete::{digit1, not_line_ending},
combinator::{map, map_res},
error::Error as NomError,
multi::separated_list1,
sequence::{pair, preceded, separated_pair},
IResult,
};
*/
pub use melib::thread::{SortField, SortOrder};
use melib::Error;
use melib::{
nom::{
self,
branch::alt,
bytes::complete::{is_a, is_not, tag, take_until},
character::complete::{digit1, not_line_ending},
combinator::{map, map_res},
error::Error as NomError,
multi::separated_list1,
sequence::{pair, preceded, separated_pair},
IResult,
},
Error,
};
use crate::melib::parser::BytesExt;
pub mod actions;
use actions::MailboxOperation;
use std::collections::HashSet;
use actions::MailboxOperation;
pub mod history;
pub use crate::actions::AccountAction::{self, *};
pub use crate::actions::Action::{self, *};
pub use crate::actions::ComposeAction::{self, *};
pub use crate::actions::ListingAction::{self, *};
pub use crate::actions::MailingListAction::{self, *};
pub use crate::actions::TabAction::{self, *};
pub use crate::actions::TagAction::{self, *};
pub use crate::actions::ViewAction::{self, *};
use std::str::FromStr;
pub use crate::actions::{
AccountAction::{self, *},
Action::{self, *},
ComposeAction::{self, *},
ListingAction::{self, *},
MailingListAction::{self, *},
TabAction::{self, *},
TagAction::{self, *},
ViewAction::{self, *},
};
/// Helper macro to convert an array of tokens into a TokenStream
macro_rules! to_stream {
($token: expr) => {
@ -63,7 +70,8 @@ macro_rules! to_stream {
};
}
/// Macro to create a const table with every command part that can be auto-completed and its description
/// Macro to create a const table with every command part that can be
/// auto-completed and its description
macro_rules! define_commands {
( [$({ tags: [$( $tags:literal),*], desc: $desc:literal, tokens: $tokens:expr, parser: ($parser:item)}),*]) => {
pub const COMMAND_COMPLETION: &[(&str, &str, TokenStream)] = &[$($( ($tags, $desc, TokenStream { tokens: $tokens } ) ),*),* ];
@ -142,7 +150,8 @@ impl TokenStream {
| t @ QuotedStringValue
| t @ AlphanumericStringValue => {
let _t = t;
//sugg.insert(format!("{}{:?}", if s.is_empty() { " " } else { "" }, t));
//sugg.insert(format!("{}{:?}", if s.is_empty() { " " }
// else { "" }, t));
}
}
tokens.push((*s, *t.inner()));
@ -209,7 +218,8 @@ impl TokenStream {
}
}
/// `Token` wrapper that defines how many times a token is expected to be repeated
/// `Token` wrapper that defines how many times a token is expected to be
/// repeated
#[derive(Debug, Copy, Clone)]
pub enum TokenAdicity {
ZeroOrOne(Token),

View File

@ -23,10 +23,12 @@
* User actions that need to be handled by the UI
*/
use crate::components::Component;
use std::path::PathBuf;
pub use melib::thread::{SortField, SortOrder};
use melib::uuid::Uuid;
use std::path::PathBuf;
use crate::components::Component;
#[derive(Debug)]
pub enum TagAction {

View File

@ -19,9 +19,11 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use std::fs::OpenOptions;
use std::io::{Read, Write};
use std::sync::{Arc, Mutex};
use std::{
fs::OpenOptions,
io::{Read, Write},
sync::{Arc, Mutex},
};
thread_local!(static CMD_HISTORY_FILE: Arc<Mutex<std::fs::File>> = Arc::new(Mutex::new({
let data_dir = xdg::BaseDirectories::with_prefix("meli").unwrap();

View File

@ -21,13 +21,16 @@
/*! Components visual and logical separations of application interfaces.
*
* They can draw on the terminal and receive events, but also do other stuff as well. (For example, see the `notifications` module.)
* They can draw on the terminal and receive events, but also do other stuff
* as well. (For example, see the `notifications` module.)
* See the `Component` Trait for more details.
*/
use super::*;
use crate::melib::text_processing::{TextProcessing, Truncate};
use crate::terminal::boundaries::*;
use crate::{
melib::text_processing::{TextProcessing, Truncate},
terminal::boundaries::*,
};
pub mod mail;
pub use crate::mail::*;
@ -46,8 +49,10 @@ pub use self::mailbox_management::*;
#[cfg(feature = "svgscreenshot")]
pub mod svg;
use std::fmt;
use std::fmt::{Debug, Display};
use std::{
fmt,
fmt::{Debug, Display},
};
use indexmap::IndexMap;
use uuid::Uuid;
@ -86,8 +91,9 @@ pub enum ScrollUpdate {
}
/// Types implementing this Trait can draw on the terminal and receive events.
/// If a type wants to skip drawing if it has not changed anything, it can hold some flag in its
/// fields (eg self.dirty = false) and act upon that in their `draw` implementation.
/// If a type wants to skip drawing if it has not changed anything, it can hold
/// some flag in its fields (eg self.dirty = false) and act upon that in their
/// `draw` implementation.
pub trait Component: Display + Debug + Send + Sync {
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context);
fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool;

View File

@ -19,9 +19,10 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use super::*;
use std::collections::HashMap;
use super::*;
mod contact_list;
pub use self::contact_list::*;

View File

@ -18,12 +18,12 @@
* You should have received a copy of the GNU General Public License
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use std::cmp;
use melib::{backends::AccountHash, CardId};
use super::*;
use crate::melib::text_processing::TextProcessing;
use melib::backends::AccountHash;
use melib::CardId;
use std::cmp;
#[derive(Debug, PartialEq, Eq)]
enum ViewMode {
@ -69,7 +69,7 @@ pub struct ContactList {
impl fmt::Display for ContactList {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", "contact list")
write!(f, "contact list")
}
}
@ -436,8 +436,8 @@ impl ContactList {
)));
}
/* If cursor position has changed, remove the highlight from the previous position and
* apply it in the new one. */
/* If cursor position has changed, remove the highlight from the previous
* position and apply it in the new one. */
if self.cursor_pos != self.new_cursor_pos && prev_page_no == page_no {
let old_cursor_pos = self.cursor_pos;
self.cursor_pos = self.new_cursor_pos;
@ -464,7 +464,7 @@ impl ContactList {
let width = width!(area);
self.data_columns.widths = Default::default();
self.data_columns.widths[0] = self.data_columns.columns[0].size().0; /* name */
self.data_columns.widths[1] = self.data_columns.columns[1].size().0; /* email*/
self.data_columns.widths[1] = self.data_columns.columns[1].size().0; /* email */
self.data_columns.widths[2] = self.data_columns.columns[2].size().0; /* url */
self.data_columns.widths[3] = self.data_columns.columns[3].size().0; /* source */
@ -783,7 +783,7 @@ impl Component for ContactList {
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
return true;
}
UIEvent::Input(Key::Char(c)) if ('0'..='9').contains(&c) => {
UIEvent::Input(Key::Char(c)) if c.is_ascii_digit() => {
self.cmd_buf.push(c);
context
.replies

View File

@ -21,10 +21,13 @@
/*! Entities that handle Mail specific functions.
*/
use melib::{
backends::{AccountHash, Mailbox, MailboxHash},
email::{attachment_types::*, attachments::*},
thread::ThreadNodeHash,
};
use super::*;
use melib::backends::{AccountHash, Mailbox, MailboxHash};
use melib::email::{attachment_types::*, attachments::*};
use melib::thread::ThreadNodeHash;
pub mod listing;
pub use crate::listing::*;

View File

@ -19,21 +19,23 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use super::*;
use melib::email::attachment_types::{ContentType, MultipartType};
use melib::list_management;
use melib::Draft;
use std::{
convert::TryInto,
future::Future,
pin::Pin,
process::{Command, Stdio},
sync::{Arc, Mutex},
};
use crate::conf::accounts::JobRequest;
use crate::jobs::JoinHandle;
use crate::terminal::embed::EmbedTerminal;
use indexmap::IndexSet;
use melib::{
email::attachment_types::{ContentType, MultipartType},
list_management, Draft,
};
use nix::sys::wait::WaitStatus;
use std::convert::TryInto;
use std::future::Future;
use std::pin::Pin;
use std::process::{Command, Stdio};
use std::sync::{Arc, Mutex};
use super::*;
use crate::{conf::accounts::JobRequest, jobs::JoinHandle, terminal::embed::EmbedTerminal};
#[cfg(feature = "gpgme")]
mod gpg;
@ -373,8 +375,9 @@ impl Composer {
let mut ret = Composer::reply_to(coordinates, reply_body, context, false);
let account = &context.accounts[&coordinates.0];
let parent_message = account.collection.get_env(coordinates.2);
/* If message is from a mailing list and we detect a List-Post header, ask user if they
* want to reply to the mailing list or the submitter of the message */
/* If message is from a mailing list and we detect a List-Post header, ask
* user if they want to reply to the mailing list or the submitter of
* the message */
if let Some(actions) = list_management::ListActions::detect(&parent_message) {
if let Some(post) = actions.post {
if let list_management::ListAction::Email(list_post_addr) = post[0] {
@ -1104,20 +1107,27 @@ impl Component for Composer {
)));
self.mode = ViewMode::WaitingForSendResult(
UIDialog::new(
"Waiting for confirmation.. The tab will close automatically on successful submission.",
"Waiting for confirmation.. The tab will close automatically \
on successful submission.",
vec![
('c', "force close tab".to_string()),
('n', "close this message and return to edit mode".to_string()),
('c', "force close tab".to_string()),
(
'n',
"close this message and return to edit mode"
.to_string(),
),
],
true,
Some(Box::new(move |id: ComponentId, results: &[char]| {
Some(UIEvent::FinishedUIDialog(
id,
Box::new(results.first().cloned().unwrap_or('c')),
id,
Box::new(results.first().cloned().unwrap_or('c')),
))
})),
context,
), handle);
),
handle,
);
}
Err(err) => {
context.replies.push_back(UIEvent::Notification(
@ -1680,10 +1690,12 @@ impl Component for Composer {
match std::env::var("EDITOR") {
Err(err) => {
context.replies.push_back(UIEvent::Notification(
Some(err.to_string()),
"$EDITOR is not set. You can change an envvar's value with setenv or set composing.editor_command setting in your configuration.".to_string(),
Some(NotificationType::Error(melib::error::ErrorKind::None)),
));
Some(err.to_string()),
"$EDITOR is not set. You can change an envvar's value with setenv \
or set composing.editor_command setting in your configuration."
.to_string(),
Some(NotificationType::Error(melib::error::ErrorKind::None)),
));
return true;
}
Ok(v) => v,
@ -1744,7 +1756,7 @@ impl Component for Composer {
DEBUG,
);
match Command::new("sh")
.args(&["-c", &editor_command])
.args(["-c", &editor_command])
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.spawn()
@ -1798,7 +1810,7 @@ impl Component for Composer {
}
let f = create_temp_file(&[], None, None, true);
match Command::new("sh")
.args(&["-c", command])
.args(["-c", command])
.stdin(Stdio::null())
.stdout(Stdio::from(f.file()))
.spawn()
@ -1886,7 +1898,7 @@ impl Component for Composer {
DEBUG,
);
match Command::new("sh")
.args(&["-c", command])
.args(["-c", command])
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::piped())

View File

@ -21,18 +21,13 @@
use super::*;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
pub enum EditAttachmentCursor {
AttachmentNo(usize),
#[default]
Buttons,
}
impl Default for EditAttachmentCursor {
fn default() -> Self {
EditAttachmentCursor::Buttons
}
}
#[derive(Debug)]
pub enum EditAttachmentMode {
Overview,

View File

@ -19,27 +19,32 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use super::*;
use crate::conf::accounts::JobRequest;
use crate::types::segment_tree::SegmentTree;
use std::{
collections::{BTreeSet, HashMap, HashSet},
convert::TryFrom,
ops::{Deref, DerefMut},
};
use melib::backends::EnvelopeHashBatch;
use smallvec::SmallVec;
use std::collections::{BTreeSet, HashMap, HashSet};
use std::convert::TryFrom;
use std::ops::{Deref, DerefMut};
// TODO: emoji_text_presentation_selector should be printed along with the chars before it but not
// as a separate Cell
use super::*;
use crate::{conf::accounts::JobRequest, types::segment_tree::SegmentTree};
// TODO: emoji_text_presentation_selector should be printed along with the chars
// before it but not as a separate Cell
//macro_rules! emoji_text_presentation_selector {
// () => {
// "\u{FE0E}"
// };
//}
//
//pub const DEFAULT_ATTACHMENT_FLAG: &str = concat!("📎", emoji_text_presentation_selector!());
//pub const DEFAULT_SELECTED_FLAG: &str = concat!("☑️", emoji_text_presentation_selector!());
//pub const DEFAULT_UNSEEN_FLAG: &str = concat!("●", emoji_text_presentation_selector!());
//pub const DEFAULT_SNOOZED_FLAG: &str = concat!("💤", emoji_text_presentation_selector!());
//pub const DEFAULT_ATTACHMENT_FLAG: &str = concat!("📎",
// emoji_text_presentation_selector!()); pub const DEFAULT_SELECTED_FLAG: &str =
// concat!("☑️", emoji_text_presentation_selector!());
// pub const DEFAULT_UNSEEN_FLAG: &str = concat!("●",
// emoji_text_presentation_selector!()); pub const DEFAULT_SNOOZED_FLAG: &str =
// concat!("💤", emoji_text_presentation_selector!());
pub const DEFAULT_ATTACHMENT_FLAG: &str = "📎";
pub const DEFAULT_SELECTED_FLAG: &str = "☑️";
@ -225,20 +230,15 @@ pub enum Focus {
EntryFullscreen,
}
#[derive(Debug, Copy, PartialEq, Eq, Clone)]
#[derive(Debug, Copy, PartialEq, Eq, Clone, Default)]
pub enum Modifier {
#[default]
SymmetricDifference,
Union,
Difference,
Intersection,
}
impl Default for Modifier {
fn default() -> Self {
Modifier::SymmetricDifference
}
}
#[derive(Debug, Default)]
/// Save theme colors to avoid looking them up again and again from settings
pub struct ColorCache {
@ -601,10 +601,9 @@ pub trait MailListingTrait: ListingTrait {
}
}
ListingAction::ExportMbox(format, ref path) => {
use std::{future::Future, io::Write, pin::Pin};
use futures::future::try_join_all;
use std::future::Future;
use std::io::Write;
use std::pin::Pin;
let futures: Result<Vec<_>> = envs_to_set
.iter()
@ -725,7 +724,8 @@ pub trait MailListingTrait: ListingTrait {
) {
}
/// Use `force` when there have been changes in the mailbox or account lists in `context`
/// Use `force` when there have been changes in the mailbox or account lists
/// in `context`
fn refresh_mailbox(&mut self, context: &mut Context, force: bool);
}
@ -1357,7 +1357,7 @@ impl Component for Listing {
.mailbox_by_path(mailbox_path)
.and_then(|mailbox_hash| {
Ok((
std::fs::read(&file_path).chain_err_summary(|| {
std::fs::read(file_path).chain_err_summary(|| {
format!("Could not read {}", file_path.display())
})?,
mailbox_hash,
@ -1716,7 +1716,8 @@ impl Component for Listing {
*account_cursor += 1;
*entry_cursor = MenuEntryCursor::Status;
}
/* If current account has no mailboxes and there is no next account, return true */
/* If current account has no mailboxes and there is no next
* account, return true */
(_, MenuEntryCursor::Status) => {
return true;
}
@ -1929,7 +1930,7 @@ impl Component for Listing {
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
return true;
}
UIEvent::Input(Key::Char(c)) if ('0'..='9').contains(&c) => {
UIEvent::Input(Key::Char(c)) if c.is_ascii_digit() => {
self.cmd_buf.push(c);
self.component.set_modifier_active(true);
context
@ -2309,8 +2310,8 @@ impl Listing {
let mut branches = String::with_capacity(16);
// What depth to skip if a mailbox is toggled to collapse
// The value should be the collapsed mailbox's indentation, so that its children are not
// visible.
// The value should be the collapsed mailbox's indentation, so that its children
// are not visible.
let mut skip: Option<usize> = None;
let mut skipped_counter: usize = 0;
'grid_loop: for y in get_y(upper_left) + 1..get_y(bottom_right) {
@ -2381,8 +2382,8 @@ impl Listing {
)
};
/* Calculate how many columns the mailbox index tags should occupy with right alignment,
* eg.
/* Calculate how many columns the mailbox index tags should occupy with right
* alignment, eg.
* 1
* 2
* ...

View File

@ -19,14 +19,12 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use super::*;
use crate::components::PageMovement;
use crate::jobs::JoinHandle;
use std::{cmp, collections::BTreeMap, convert::TryInto, iter::FromIterator};
use indexmap::IndexSet;
use std::cmp;
use std::collections::BTreeMap;
use std::convert::TryInto;
use std::iter::FromIterator;
use super::*;
use crate::{components::PageMovement, jobs::JoinHandle};
macro_rules! digits_of_num {
($num:expr) => {{
@ -158,8 +156,8 @@ macro_rules! row_attr {
}};
}
/// A list of all mail (`Envelope`s) in a `Mailbox`. On `\n` it opens the `Envelope` content in a
/// `ThreadView`.
/// A list of all mail (`Envelope`s) in a `Mailbox`. On `\n` it opens the
/// `Envelope` content in a `ThreadView`.
#[derive(Debug)]
pub struct CompactListing {
/// (x, y, z): x is accounts, y is mailboxes, z is index inside a mailbox.
@ -239,8 +237,8 @@ impl MailListingTrait for CompactListing {
SmallVec::from_iter(iter)
}
/// Fill the `self.data_columns` `CellBuffers` with the contents of the account mailbox the user has
/// chosen.
/// Fill the `self.data_columns` `CellBuffers` with the contents of the
/// account mailbox the user has chosen.
fn refresh_mailbox(&mut self, context: &mut Context, force: bool) {
self.set_dirty(true);
self.rows.clear();
@ -565,7 +563,7 @@ impl ListingTrait for CompactListing {
fn set_coordinates(&mut self, coordinates: (AccountHash, MailboxHash)) {
self.new_cursor_pos = (coordinates.0, coordinates.1, 0);
self.focus = Focus::None;
self.view = Box::new(ThreadView::default());
self.view = Box::<ThreadView>::default();
self.filtered_selection.clear();
self.filtered_order.clear();
self.filter_term.clear();
@ -692,8 +690,8 @@ impl ListingTrait for CompactListing {
let end_idx = cmp::min(self.length.saturating_sub(1), top_idx + rows - 1);
self.draw_rows(context, top_idx, end_idx);
/* If cursor position has changed, remove the highlight from the previous position and
* apply it in the new one. */
/* If cursor position has changed, remove the highlight from the previous
* position and apply it in the new one. */
if self.cursor_pos.2 != self.new_cursor_pos.2 && prev_page_no == page_no {
let old_cursor_pos = self.cursor_pos;
self.cursor_pos = self.new_cursor_pos;
@ -840,9 +838,10 @@ impl ListingTrait for CompactListing {
self.view
.process_event(&mut UIEvent::VisibilityChange(false), context);
self.dirty = true;
/* If self.rows.row_updates is not empty and we exit a thread, the row_update events
* will be performed but the list will not be drawn. So force a draw in any case.
* */
/* If self.rows.row_updates is not empty and we exit a thread, the row_update
* events will be performed but the list will not be drawn.
* So force a draw in any case.
*/
self.force_draw = true;
}
Focus::Entry => {
@ -889,7 +888,7 @@ impl CompactListing {
rows: RowsState::default(),
dirty: true,
force_draw: true,
view: Box::new(ThreadView::default()),
view: Box::<ThreadView>::default(),
color_cache: ColorCache::default(),
movement: None,
modifier_active: false,
@ -1064,8 +1063,8 @@ impl CompactListing {
let account = &context.accounts[&self.cursor_pos.0];
if !account.contains_key(env_hash) {
/* The envelope has been renamed or removed, so wait for the appropriate event to
* arrive */
/* The envelope has been renamed or removed, so wait for the appropriate
* event to arrive */
return;
}
let tags_lck = account.collection.tag_index.read().unwrap();

View File

@ -19,12 +19,12 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use super::*;
use crate::components::PageMovement;
use crate::jobs::JoinHandle;
use std::{collections::BTreeMap, iter::FromIterator};
use indexmap::IndexSet;
use std::collections::BTreeMap;
use std::iter::FromIterator;
use super::*;
use crate::{components::PageMovement, jobs::JoinHandle};
macro_rules! row_attr {
($field:ident, $color_cache:expr, $unseen:expr, $highlighted:expr, $selected:expr $(,)*) => {{
@ -91,8 +91,8 @@ macro_rules! row_attr {
}};
}
/// A list of all mail (`Envelope`s) in a `Mailbox`. On `\n` it opens the `Envelope` content in a
/// `ThreadView`.
/// A list of all mail (`Envelope`s) in a `Mailbox`. On `\n` it opens the
/// `Envelope` content in a `ThreadView`.
#[derive(Debug)]
pub struct ConversationsListing {
/// (x, y, z): x is accounts, y is mailboxes, z is index inside a mailbox.
@ -455,8 +455,8 @@ impl ListingTrait for ConversationsListing {
let top_idx = page_no * rows;
/* If cursor position has changed, remove the highlight from the previous position and
* apply it in the new one. */
/* If cursor position has changed, remove the highlight from the previous
* position and apply it in the new one. */
if self.cursor_pos.2 != self.new_cursor_pos.2 && prev_page_no == page_no {
let old_cursor_pos = self.cursor_pos;
self.cursor_pos = self.new_cursor_pos;
@ -582,9 +582,10 @@ impl ListingTrait for ConversationsListing {
self.view
.process_event(&mut UIEvent::VisibilityChange(false), context);
self.dirty = true;
/* If self.rows.row_updates is not empty and we exit a thread, the row_update events
* will be performed but the list will not be drawn. So force a draw in any case.
* */
/* If self.rows.row_updates is not empty and we exit a thread, the row_update
* events will be performed but the list will not be drawn.
* So force a draw in any case.
*/
self.force_draw = true;
}
Focus::Entry => {
@ -1181,7 +1182,7 @@ impl Component for ConversationsListing {
if !self.rows.row_updates.is_empty() {
/* certain rows need to be updated (eg an unseen message was just set seen)
* */
*/
while let Some(row) = self.rows.row_updates.pop() {
self.update_line(context, row);
let row: usize = self.rows.env_order[&row];
@ -1379,7 +1380,8 @@ impl Component for ConversationsListing {
// FIXME subsort
//if !self.filtered_selection.is_empty() {
// let threads = &account.collection.threads[&self.cursor_pos.1];
// threads.vec_inner_sort_by(&mut self.filtered_selection, self.sort, &account.collection);
// threads.vec_inner_sort_by(&mut self.filtered_selection, self.sort,
// &account.collection);
//} else {
// self.refresh_mailbox(context, false);
//}

View File

@ -19,9 +19,10 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use std::borrow::Cow;
use super::*;
use crate::components::PageMovement;
use std::borrow::Cow;
#[derive(Debug)]
pub struct OfflineListing {

View File

@ -19,12 +19,10 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use super::EntryStrings;
use super::*;
use crate::components::PageMovement;
use crate::jobs::JoinHandle;
use std::cmp;
use std::iter::FromIterator;
use std::{cmp, iter::FromIterator};
use super::{EntryStrings, *};
use crate::{components::PageMovement, jobs::JoinHandle};
macro_rules! address_list {
(($name:expr) as comma_sep_list) => {{
@ -118,8 +116,8 @@ macro_rules! row_attr {
}};
}
/// A list of all mail (`Envelope`s) in a `Mailbox`. On `\n` it opens the `Envelope` content in a
/// `MailView`.
/// A list of all mail (`Envelope`s) in a `Mailbox`. On `\n` it opens the
/// `Envelope` content in a `MailView`.
#[derive(Debug)]
pub struct PlainListing {
/// (x, y, z): x is accounts, y is mailboxes, z is index inside a mailbox.
@ -182,8 +180,8 @@ impl MailListingTrait for PlainListing {
)
}
/// Fill the `self.data_columns` `CellBuffers` with the contents of the account mailbox the user has
/// chosen.
/// Fill the `self.data_columns` `CellBuffers` with the contents of the
/// account mailbox the user has chosen.
fn refresh_mailbox(&mut self, context: &mut Context, force: bool) {
self.set_dirty(true);
let old_cursor_pos = self.cursor_pos;
@ -423,8 +421,8 @@ impl ListingTrait for PlainListing {
let top_idx = page_no * rows;
/* If cursor position has changed, remove the highlight from the previous position and
* apply it in the new one. */
/* If cursor position has changed, remove the highlight from the previous
* position and apply it in the new one. */
if self.cursor_pos.2 != self.new_cursor_pos.2 && prev_page_no == page_no {
let old_cursor_pos = self.cursor_pos;
self.cursor_pos = self.new_cursor_pos;
@ -571,9 +569,10 @@ impl ListingTrait for PlainListing {
self.view
.process_event(&mut UIEvent::VisibilityChange(false), context);
self.dirty = true;
/* If self.rows.row_updates is not empty and we exit a thread, the row_update events
* will be performed but the list will not be drawn. So force a draw in any case.
* */
/* If self.rows.row_updates is not empty and we exit a thread, the row_update
* events will be performed but the list will not be drawn.
* So force a draw in any case.
*/
self.force_draw = true;
}
Focus::Entry => {
@ -973,8 +972,8 @@ impl PlainListing {
let account = &context.accounts[&self.cursor_pos.0];
if !account.contains_key(env_hash) {
/* The envelope has been renamed or removed, so wait for the appropriate event to
* arrive */
/* The envelope has been renamed or removed, so wait for the appropriate
* event to arrive */
return;
}
let envelope: EnvelopeRef = account.collection.get_env(env_hash);
@ -1425,7 +1424,8 @@ impl Component for PlainListing {
self.subsort = (*field, *order);
//if !self.filtered_selection.is_empty() {
// let threads = &account.collection.threads[&self.cursor_pos.1];
// threads.vec_inner_sort_by(&mut self.filtered_selection, self.sort, &account.collection);
// threads.vec_inner_sort_by(&mut self.filtered_selection, self.sort,
// &account.collection);
//} else {
// self.refresh_mailbox(contex, falset);
//}

View File

@ -19,12 +19,10 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use std::{cmp, convert::TryInto, fmt::Write, iter::FromIterator};
use super::*;
use crate::components::PageMovement;
use std::cmp;
use std::convert::TryInto;
use std::fmt::Write;
use std::iter::FromIterator;
macro_rules! row_attr {
($color_cache:expr, $even: expr, $unseen:expr, $highlighted:expr, $selected:expr $(,)*) => {{
@ -102,8 +100,8 @@ macro_rules! row_attr {
}};
}
/// A list of all mail (`Envelope`s) in a `Mailbox`. On `\n` it opens the `Envelope` content in a
/// `MailView`.
/// A list of all mail (`Envelope`s) in a `Mailbox`. On `\n` it opens the
/// `Envelope` content in a `MailView`.
#[derive(Debug)]
pub struct ThreadListing {
/// (x, y, z): x is accounts, y is mailboxes, z is index inside a mailbox.
@ -167,8 +165,8 @@ impl MailListingTrait for ThreadListing {
)
}
/// Fill the `self.content` `CellBuffer` with the contents of the account mailbox the user has
/// chosen.
/// Fill the `self.content` `CellBuffer` with the contents of the account
/// mailbox the user has chosen.
fn refresh_mailbox(&mut self, context: &mut Context, _force: bool) {
self.set_dirty(true);
if !(self.cursor_pos.0 == self.new_cursor_pos.0
@ -485,8 +483,8 @@ impl ListingTrait for ThreadListing {
let top_idx = page_no * rows;
/* If cursor position has changed, remove the highlight from the previous position and
* apply it in the new one. */
/* If cursor position has changed, remove the highlight from the previous
* position and apply it in the new one. */
if self.cursor_pos.2 != self.new_cursor_pos.2 && prev_page_no == page_no {
let old_cursor_pos = self.cursor_pos;
self.cursor_pos = self.new_cursor_pos;
@ -634,9 +632,10 @@ impl ListingTrait for ThreadListing {
Focus::None => {
self.view = None;
self.dirty = true;
/* If self.rows.row_updates is not empty and we exit a thread, the row_update events
* will be performed but the list will not be drawn. So force a draw in any case.
* */
/* If self.rows.row_updates is not empty and we exit a thread, the row_update
* events will be performed but the list will not be drawn.
* So force a draw in any case.
*/
self.force_draw = true;
}
Focus::Entry => {
@ -982,8 +981,8 @@ impl ThreadListing {
let account = &context.accounts[&self.cursor_pos.0];
if !account.contains_key(env_hash) {
/* The envelope has been renamed or removed, so wait for the appropriate event to
* arrive */
/* The envelope has been renamed or removed, so wait for the appropriate
* event to arrive */
return;
}
let envelope: EnvelopeRef = account.collection.get_env(env_hash);

View File

@ -22,15 +22,17 @@
//FIXME
#![allow(unused_imports, unused_variables)]
use melib::email::{
attachment_types::{ContentDisposition, ContentType, MultipartType},
pgp as melib_pgp, Attachment, AttachmentBuilder,
use std::{future::Future, pin::Pin};
use melib::{
email::{
attachment_types::{ContentDisposition, ContentType, MultipartType},
pgp as melib_pgp, Attachment, AttachmentBuilder,
},
error::*,
gpgme::*,
parser::BytesExt,
};
use melib::error::*;
use melib::gpgme::*;
use melib::parser::BytesExt;
use std::future::Future;
use std::pin::Pin;
pub async fn decrypt(raw: Vec<u8>) -> Result<(melib_pgp::DecryptionMetadata, Vec<u8>)> {
Err("libgpgme functions are temporarily disabled due to an unsolved bug <https://git.meli.delivery/meli/meli/issues/176>.".into())

View File

@ -33,7 +33,7 @@ pub struct AccountStatus {
impl fmt::Display for AccountStatus {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", "status")
write!(f, "status")
}
}
@ -368,8 +368,8 @@ impl Component for AccountStatus {
}
}
/* self.content may have been resized with write_string_to_grid() calls above since it has
* growable set */
/* self.content may have been resized with write_string_to_grid() calls above
* since it has growable set */
let (width, height) = self.content.size();
let (cols, rows) = (width!(area), height!(area));
self.cursor = (

View File

@ -19,20 +19,23 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use super::*;
use crate::conf::accounts::JobRequest;
use crate::jobs::{JobId, JoinHandle};
use melib::email::attachment_types::ContentType;
use melib::list_management;
use melib::parser::BytesExt;
use smallvec::SmallVec;
use std::collections::HashSet;
use std::fmt::Write as _;
use std::io::Write;
use std::{
collections::HashSet,
convert::TryFrom,
fmt::Write as _,
io::Write,
os::unix::fs::PermissionsExt,
process::{Command, Stdio},
};
use std::convert::TryFrom;
use std::os::unix::fs::PermissionsExt;
use std::process::{Command, Stdio};
use melib::{email::attachment_types::ContentType, list_management, parser::BytesExt};
use smallvec::SmallVec;
use super::*;
use crate::{
conf::accounts::JobRequest,
jobs::{JobId, JoinHandle},
};
mod html;
pub use self::html::*;
@ -40,11 +43,11 @@ mod thread;
pub use self::thread::*;
mod envelope;
pub use self::envelope::*;
use linkify::LinkFinder;
use xdg_utils::query_default_app;
pub use self::envelope::*;
#[derive(Debug, Default)]
enum ForceCharset {
#[default]
@ -68,8 +71,9 @@ enum Source {
Raw,
}
#[derive(PartialEq, Debug)]
#[derive(PartialEq, Debug, Default)]
enum ViewMode {
#[default]
Normal,
Url,
Attachment(usize),
@ -79,12 +83,6 @@ enum ViewMode {
ContactSelector(Box<UIDialog<Card>>),
}
impl Default for ViewMode {
fn default() -> Self {
ViewMode::Normal
}
}
impl ViewMode {
/*
fn is_ansi(&self) -> bool {
@ -158,8 +156,8 @@ pub enum AttachmentDisplay {
},
}
/// Contains an Envelope view, with sticky headers, a pager for the body, and subviews for more
/// menus
/// Contains an Envelope view, with sticky headers, a pager for the body, and
/// subviews for more menus
#[derive(Debug, Default)]
pub struct MailView {
coordinates: (AccountHash, MailboxHash, EnvelopeHash),
@ -322,7 +320,7 @@ impl Clone for MailView {
impl fmt::Display for MailView {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", "view mail")
write!(f, "view mail")
}
}
@ -739,7 +737,7 @@ impl MailView {
branches,
paths,
cur_path,
iter.peek() != None,
iter.peek().is_some(),
s,
);
if Some(i) == default_alternative {
@ -795,7 +793,7 @@ impl MailView {
.map(|s| s.as_str())
.unwrap_or("w3m -I utf-8 -T text/html");
let command_obj = Command::new("sh")
.args(&["-c", filter_invocation])
.args(["-c", filter_invocation])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn();
@ -810,9 +808,10 @@ impl MailView {
Some(NotificationType::Error(melib::ErrorKind::External)),
));
let comment = Some(format!(
"Failed to start html filter process: `{}`. Press `v` to open in web browser. \n\n",
filter_invocation
));
"Failed to start html filter process: `{}`. Press `v` to open in web \
browser. \n\n",
filter_invocation
));
let text = String::from_utf8_lossy(&bytes).to_string();
acc.push(AttachmentDisplay::InlineText {
inner: Box::new(a.clone()),
@ -975,9 +974,12 @@ impl MailView {
#[cfg(not(feature = "gpgme"))]
{
acc.push(AttachmentDisplay::EncryptedFailed {
inner: Box::new(a.clone()),
error: Error::new("Cannot decrypt: meli must be compiled with libgpgme support."),
});
inner: Box::new(a.clone()),
error: Error::new(
"Cannot decrypt: meli must be compiled with libgpgme \
support.",
),
});
}
#[cfg(feature = "gpgme")]
{
@ -1185,8 +1187,8 @@ impl Component for MailView {
let y: usize = {
let account = &context.accounts[&self.coordinates.0];
if !account.contains_key(self.coordinates.2) {
/* The envelope has been renamed or removed, so wait for the appropriate event to
* arrive */
/* The envelope has been renamed or removed, so wait for the appropriate
* event to arrive */
return;
}
let envelope: EnvelopeRef = account.collection.get_env(self.coordinates.2);
@ -1866,7 +1868,8 @@ impl Component for MailView {
} if handle.job_id == *job_id => {
match handle.chan.try_recv() {
Err(_) => { /* Job was canceled */ }
Ok(None) => { /* something happened, perhaps a worker thread panicked */
Ok(None) => { /* something happened, perhaps a worker
* thread panicked */
}
Ok(Some(Ok(bytes))) => {
MailViewState::load_bytes(self, bytes, context);
@ -1895,7 +1898,9 @@ impl Component for MailView {
self.initialised = false;
match handle.chan.try_recv() {
Err(_) => { /* Job was canceled */ }
Ok(None) => { /* something happened, perhaps a worker thread panicked */
Ok(None) => { /* something happened,
* perhaps a worker thread
* panicked */
}
Ok(Some(Ok(()))) => {
*d = AttachmentDisplay::SignedVerified {
@ -1930,7 +1935,9 @@ impl Component for MailView {
self.initialised = false;
match handle.chan.try_recv() {
Err(_) => { /* Job was canceled */ }
Ok(None) => { /* something happened, perhaps a worker thread panicked */
Ok(None) => { /* something happened,
* perhaps a worker thread
* panicked */
}
Ok(Some(Ok((metadata, decrypted_bytes)))) => {
let plaintext = Box::new(
@ -2108,35 +2115,36 @@ impl Component for MailView {
on_finish: Some(CallbackFn(Box::new(move |context: &mut Context| {
match receiver.try_recv() {
Err(_) => { /* Job was canceled */ }
Ok(None) => { /* something happened, perhaps a worker thread panicked */
Ok(None) => { /* something happened, perhaps a worker
* thread panicked */
}
Ok(Some(result)) => {
match result.and_then(|bytes| {
Composer::edit(account_hash, env_hash, &bytes, context)
}) {
Ok(composer) => {
context.replies.push_back(UIEvent::Action(Tab(New(Some(
Box::new(composer),
)))));
context.replies.push_back(UIEvent::Action(Tab(New(
Some(Box::new(composer)),
))));
}
Err(err) => {
let err_string = format!(
"Failed to open envelope {}: {}",
context.accounts[&account_hash]
.collection
.envelopes
.read()
.unwrap()
.get(&env_hash)
.map(|env| env.message_id_display())
.unwrap_or_else(|| "Not found".into()),
.collection
.envelopes
.read()
.unwrap()
.get(&env_hash)
.map(|env| env.message_id_display())
.unwrap_or_else(|| "Not found".into()),
err
);
log(&err_string, ERROR);
context.replies.push_back(UIEvent::Notification(
Some("Failed to open e-mail".to_string()),
err_string,
Some(NotificationType::Error(err.kind)),
Some("Failed to open e-mail".to_string()),
err_string,
Some(NotificationType::Error(err.kind)),
));
}
}
@ -2176,7 +2184,7 @@ impl Component for MailView {
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
return true;
}
UIEvent::Input(Key::Char(c)) if ('0'..='9').contains(&c) => {
UIEvent::Input(Key::Char(c)) if c.is_ascii_digit() => {
self.cmd_buf.push(c);
context
.replies
@ -2316,7 +2324,7 @@ impl Component for MailView {
false,
);
match Command::new("sh")
.args(&["-c", &exec_cmd])
.args(["-c", &exec_cmd])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
@ -2336,19 +2344,22 @@ impl Component for MailView {
}
} else {
context.replies.push_back(UIEvent::StatusEvent(
StatusEvent::DisplayMessage(if let Some(filename) = filename.as_ref() {
format!(
"Couldn't find a default application for file {} (type {})",
filename,
attachment_type
)
} else {
format!(
"Couldn't find a default application for type {}",
attachment_type
)
}),
));
StatusEvent::DisplayMessage(
if let Some(filename) = filename.as_ref() {
format!(
"Couldn't find a default application for \
file {} (type {})",
filename, attachment_type
)
} else {
format!(
"Couldn't find a default application for \
type {}",
attachment_type
)
},
),
));
}
}
ContentType::OctetStream {
@ -2357,9 +2368,10 @@ impl Component for MailView {
} => {
context.replies.push_back(UIEvent::StatusEvent(
StatusEvent::DisplayMessage(format!(
"Failed to open {}. application/octet-stream isn't supported yet",
name.as_ref().map(|n| n.as_str()).unwrap_or("file")
)),
"Failed to open {}. application/octet-stream isn't \
supported yet",
name.as_ref().map(|n| n.as_str()).unwrap_or("file")
)),
));
}
}
@ -2479,8 +2491,8 @@ impl Component for MailView {
// Save entire message as eml
let account = &context.accounts[&self.coordinates.0];
if !account.contains_key(self.coordinates.2) {
/* The envelope has been renamed or removed, so wait for the appropriate event to
* arrive */
/* The envelope has been renamed or removed, so wait for the appropriate
* event to arrive */
return true;
}
let bytes = if let MailViewState::Loaded { ref bytes, .. } = self.state {
@ -2532,8 +2544,8 @@ impl Component for MailView {
{
let account = &context.accounts[&self.coordinates.0];
if !account.contains_key(self.coordinates.2) {
/* The envelope has been renamed or removed, so wait for the appropriate event to
* arrive */
/* The envelope has been renamed or removed, so wait for the appropriate
* event to arrive */
return true;
}
}
@ -2626,8 +2638,8 @@ impl Component for MailView {
UIEvent::Action(MailingListAction(ref e)) => {
let account = &context.accounts[&self.coordinates.0];
if !account.contains_key(self.coordinates.2) {
/* The envelope has been renamed or removed, so wait for the appropriate event to
* arrive */
/* The envelope has been renamed or removed, so wait for the appropriate
* event to arrive */
return true;
}
let envelope: EnvelopeRef = account.collection.get_env(self.coordinates.2);
@ -2661,7 +2673,7 @@ impl Component for MailView {
return true;
}
MailingListAction::ListUnsubscribe if actions.unsubscribe.is_some() => {
/* autosend or open unsubscribe option*/
/* autosend or open unsubscribe option */
let unsubscribe = actions.unsubscribe.as_ref().unwrap();
for option in unsubscribe.iter() {
/* TODO: Ask for confirmation before proceding with an action */

View File

@ -19,12 +19,13 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use super::*;
use linkify::{Link, LinkFinder};
use std::process::{Command, Stdio};
use linkify::{Link, LinkFinder};
use xdg_utils::query_default_app;
use super::*;
#[derive(PartialEq, Eq, Debug)]
enum ViewMode {
Normal,
@ -40,8 +41,8 @@ impl ViewMode {
}
}
/// Contains an Envelope view, with sticky headers, a pager for the body, and subviews for more
/// menus
/// Contains an Envelope view, with sticky headers, a pager for the body, and
/// subviews for more menus
#[derive(Debug)]
pub struct EnvelopeView {
pager: Option<Pager>,
@ -91,7 +92,7 @@ impl EnvelopeView {
let settings = &context.settings;
if let Some(filter_invocation) = settings.pager.html_filter.as_ref() {
let command_obj = Command::new("sh")
.args(&["-c", filter_invocation])
.args(["-c", filter_invocation])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn();
@ -114,9 +115,10 @@ impl EnvelopeView {
.write_all(v)
.expect("Failed to write to stdin");
*v = format!(
"Text piped through `{}`. Press `v` to open in web browser. \n\n",
filter_invocation
)
"Text piped through `{}`. Press `v` to open in web browser. \
\n\n",
filter_invocation
)
.into_bytes();
v.extend(html_filter.wait_with_output().unwrap().stdout);
}
@ -372,7 +374,7 @@ impl Component for EnvelopeView {
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
return true;
}
UIEvent::Input(Key::Char(c)) if ('0'..='9').contains(&c) => {
UIEvent::Input(Key::Char(c)) if c.is_ascii_digit() => {
self.cmd_buf.push(c);
return true;
}
@ -449,7 +451,7 @@ impl Component for EnvelopeView {
false,
);
match Command::new("sh")
.args(&["-c", &exec_cmd])
.args(["-c", &exec_cmd])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
@ -469,18 +471,20 @@ impl Component for EnvelopeView {
}
} else {
context.replies.push_back(UIEvent::StatusEvent(
StatusEvent::DisplayMessage(if let Some(filename) = filename.as_ref() {
StatusEvent::DisplayMessage(
if let Some(filename) = filename.as_ref() {
format!(
"Couldn't find a default application for file {} (type {})",
filename,
attachment_type
"Couldn't find a default application for file {} \
(type {})",
filename, attachment_type
)
} else {
format!(
"Couldn't find a default application for type {}",
attachment_type
)
}),
},
),
));
return true;
}

View File

@ -19,9 +19,12 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use std::{
io::Write,
process::{Command, Stdio},
};
use super::*;
use std::io::Write;
use std::process::{Command, Stdio};
#[derive(Debug)]
pub struct HtmlView {
@ -40,7 +43,7 @@ impl HtmlView {
let mut display_text = if let Some(filter_invocation) = settings.pager.html_filter.as_ref()
{
let command_obj = Command::new("sh")
.args(&["-c", filter_invocation])
.args(["-c", filter_invocation])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn();
@ -74,7 +77,7 @@ impl HtmlView {
}
}
} else if let Ok(mut html_filter) = Command::new("w3m")
.args(&["-I", "utf-8", "-T", "text/html"])
.args(["-I", "utf-8", "-T", "text/html"])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
@ -154,7 +157,7 @@ impl Component for HtmlView {
let exec_cmd =
super::desktop_exec_to_command(&command, p.path.display().to_string(), false);
match Command::new("sh")
.args(&["-c", &exec_cmd])
.args(["-c", &exec_cmd])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()

View File

@ -19,9 +19,10 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use std::cmp;
use super::*;
use crate::components::PageMovement;
use std::cmp;
#[derive(Debug, Clone)]
struct ThreadEntry {
@ -62,8 +63,8 @@ pub struct ThreadView {
impl ThreadView {
/*
* coordinates: (account index, mailbox_hash, root set thread_node index)
* expanded_hash: optional position of expanded entry when we render the threadview. Default
* expanded message is the last one.
* expanded_hash: optional position of expanded entry when we render the
* threadview. Default expanded message is the last one.
* context: current context
*/
pub fn new(
@ -491,8 +492,8 @@ impl ThreadView {
let page_no = (self.new_cursor_pos).wrapping_div(rows);
let top_idx = page_no * rows;
/* This closure (written for code clarity, should be inlined by the compiler) returns the
* **line** of an entry in the ThreadView grid. */
/* This closure (written for code clarity, should be inlined by the compiler)
* returns the **line** of an entry in the ThreadView grid. */
let get_entry_area = |idx: usize, entries: &[ThreadEntry]| {
let entries = &entries;
let visual_indentation = entries[idx].index.0 * 4;
@ -530,8 +531,8 @@ impl ThreadView {
),
);
}
/* If cursor position has changed, remove the highlight from the previous position and
* apply it in the new one. */
/* If cursor position has changed, remove the highlight from the previous
* position and apply it in the new one. */
self.cursor_pos = self.new_cursor_pos;
if self.cursor_pos + 1 > visibles.len() {
self.cursor_pos = visibles.len().saturating_sub(1);
@ -588,8 +589,8 @@ impl ThreadView {
} else {
let old_cursor_pos = self.cursor_pos;
self.cursor_pos = self.new_cursor_pos;
/* If cursor position has changed, remove the highlight from the previous position and
* apply it in the new one. */
/* If cursor position has changed, remove the highlight from the previous
* position and apply it in the new one. */
let visibles: Vec<&usize> =
self.visible_entries.iter().flat_map(|v| v.iter()).collect();
for &idx in &[old_cursor_pos, self.cursor_pos] {
@ -941,7 +942,8 @@ impl ThreadView {
}
}
/// Current position in self.entries (not in drawn entries which might exclude nonvisible ones)
/// Current position in self.entries (not in drawn entries which might
/// exclude nonvisible ones)
fn current_pos(&self) -> usize {
let visibles: Vec<&usize> = self.visible_entries.iter().flat_map(|v| v.iter()).collect();
*visibles[self.new_cursor_pos]

View File

@ -18,12 +18,12 @@
* You should have received a copy of the GNU General Public License
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use super::*;
use crate::conf::accounts::MailboxEntry;
use crate::melib::text_processing::TextProcessing;
use std::cmp;
use melib::backends::AccountHash;
use std::cmp;
use super::*;
use crate::{conf::accounts::MailboxEntry, melib::text_processing::TextProcessing};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum MailboxAction {
@ -63,7 +63,7 @@ pub struct MailboxManager {
impl fmt::Display for MailboxManager {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", "mailboxes")
write!(f, "mailboxes")
}
}
@ -96,7 +96,7 @@ impl MailboxManager {
self.length = account.mailbox_entries.len();
self.entries = account.mailbox_entries.clone();
self.entries
.sort_by(|_, a, _, b| a.ref_mailbox.path().cmp(&b.ref_mailbox.path()));
.sort_by(|_, a, _, b| a.ref_mailbox.path().cmp(b.ref_mailbox.path()));
self.set_dirty(true);
let mut min_width = (
@ -268,8 +268,8 @@ impl MailboxManager {
)));
}
/* If cursor position has changed, remove the highlight from the previous position and
* apply it in the new one. */
/* If cursor position has changed, remove the highlight from the previous
* position and apply it in the new one. */
if self.cursor_pos != self.new_cursor_pos && prev_page_no == page_no {
let old_cursor_pos = self.cursor_pos;
self.cursor_pos = self.new_cursor_pos;
@ -362,11 +362,13 @@ impl Component for MailboxManager {
MailboxAction::Move | MailboxAction::Rename => {
context.replies.push_back(UIEvent::CmdInput(Key::Paste(
format!(
"rename-mailbox \"{account_name}\" \"{mailbox_path_src}\" ",
account_name = context.accounts[&self.account_hash].name(),
mailbox_path_src =
self.entries[self.cursor_pos].ref_mailbox.path()
),
"rename-mailbox \"{account_name}\" \
\"{mailbox_path_src}\" ",
account_name =
context.accounts[&self.account_hash].name(),
mailbox_path_src =
self.entries[self.cursor_pos].ref_mailbox.path()
),
)));
context
.replies

View File

@ -24,11 +24,11 @@ Notification handling components.
*/
use std::process::{Command, Stdio};
use super::*;
#[cfg(all(target_os = "linux", feature = "dbus-notifications"))]
pub use dbus::*;
use super::*;
#[cfg(all(target_os = "linux", feature = "dbus-notifications"))]
mod dbus {
use super::*;
@ -230,7 +230,20 @@ impl Component for NotificationCommand {
} else {
#[cfg(target_os = "macos")]
{
let applescript = format!("display notification \"{message}\" with title \"{title}\" subtitle \"{subtitle}\"", message = body.replace('"', "'"), title = title.as_ref().map(String::as_str).unwrap_or("meli").replace('"', "'"), subtitle = kind.map(|k| k.to_string()).unwrap_or_default().replace('"', "'"));
let applescript = format!(
"display notification \"{message}\" with title \"{title}\" subtitle \
\"{subtitle}\"",
message = body.replace('"', "'"),
title = title
.as_ref()
.map(String::as_str)
.unwrap_or("meli")
.replace('"', "'"),
subtitle = kind
.map(|k| k.to_string())
.unwrap_or_default()
.replace('"', "'")
);
match Command::new("osascript")
.arg("-e")
.arg(applescript)
@ -279,7 +292,7 @@ impl Component for NotificationCommand {
fn update_xbiff(path: &str) -> Result<()> {
let mut file = std::fs::OpenOptions::new()
.append(true) /* writes will append to a file instead of overwriting previous contents */
.create(true) /* a new file will be created if the file does not yet already exist.*/
.create(true) /* a new file will be created if the file does not yet already exist. */
.open(path)?;
if file.metadata()?.len() > 128 {
file.set_len(0)?;

View File

@ -18,9 +18,9 @@
* You should have received a copy of the GNU General Public License
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use std::{collections::BTreeMap, io::Write};
use super::*;
use std::collections::BTreeMap;
use std::io::Write;
#[derive(Debug)]
pub struct SVGScreenshotFilter {
@ -48,22 +48,29 @@ impl Component for SVGScreenshotFilter {
}
self.save_screenshot = false;
let grid: &CellBuffer = _grid;
use svg_crate::node::element::{Definitions, Group, Rectangle, Style, Text, Use};
use svg_crate::node::Text as TextNode;
use svg_crate::Document;
use svg_crate::{
node::{
element::{Definitions, Group, Rectangle, Style, Text, Use},
Text as TextNode,
},
Document,
};
let (width, height) = grid.size();
/*
* Format frame as follows:
* - The entire background is a big rectangle.
* - Every text piece with unified foreground color is a text element inserted into the
* - Every text piece with unified foreground color is a text element
* inserted into the
* `definitions` field of the svg, and then `use`ed as a reference
* - Every background piece (a slice of unified background color) is a rectangle element
* - Every background piece (a slice of unified background color) is a
* rectangle element
* inserted along with the `use` elements
*
* Each row is arbritarily set at 17px high, and each character cell is 8 pixels wide.
* Rectangle cells each have one extra pixel (so 18px * 9px) in their dimensions in order
* to cover the spacing between cells.
* Each row is arbritarily set at 17px high, and each character cell is 8
* pixels wide. Rectangle cells each have one extra pixel (so 18px *
* 9px) in their dimensions in order to cover the spacing between
* cells.
*/
let mut definitions = Definitions::new();
let mut rows_group = Group::new();
@ -79,9 +86,11 @@ impl Component for SVGScreenshotFilter {
/* Each row is a <g> group element, consisting of text elements */
let mut row_group = Group::new().set("id", format!("{:x}", row_idx + 1));
/* Keep track of colors and attributes.
* - Whenever the foreground color changes, emit a text element with the accumulated
* - Whenever the foreground color changes, emit a text element with the
* accumulated
* text in the specific foreground color.
* - Whenever the backgrund color changes, emit a rectangle element filled with the
* - Whenever the backgrund color changes, emit a rectangle element filled
* with the
* specific background color.
*/
let mut cur_fg = Color::Default;
@ -151,7 +160,7 @@ impl Component for SVGScreenshotFilter {
.add(TextNode::new(&escaped_text))
.set("x", prev_x_fg * 8)
.set("textLength", text_length * 8);
/*.set("fgname", format!("{:?}", cur_fg));*/
/* .set("fgname", format!("{:?}", cur_fg)); */
if cur_attrs.intersects(Attr::BOLD) {
text_el = text_el.set("font-weight", "bold");
}
@ -275,7 +284,7 @@ impl Component for SVGScreenshotFilter {
.add(TextNode::new(&escaped_text))
.set("x", prev_x_fg * 8)
.set("textLength", text_length * 8);
/*.set("fgname", format!("{:?}", cur_fg));*/
/* .set("fgname", format!("{:?}", cur_fg)); */
if cur_attrs.intersects(Attr::BOLD) {
text_el = text_el.set("font-weight", "bold");
}
@ -441,260 +450,260 @@ impl Component for SVGScreenshotFilter {
const CSS_STYLE: &str = r#"#t{font-family:'DejaVu Sans Mono',monospace;font-style:normal;font-size:14px;} text {dominant-baseline: text-before-edge; white-space: pre;} .f{fill:#e5e5e5;} .b{fill:#000;} .c0 {fill:#000;} .c1 {fill:#cd0000;} .c2 {fill:#00cd00;} .c3 {fill:#cdcd00;} .c4 {fill:#00e;} .c5 {fill:#cd00cd;} .c6 {fill:#00cdcd;} .c7 {fill:#e5e5e5;} .c8 {fill:#7f7f7f;} .c9 {fill:#f00;} .c10 {fill:#0f0;} .c11 {fill:#ff0;} .c12 {fill:#5c5cff;} .c13 {fill:#f0f;} .c14 {fill:#0ff;} .c15 {fill:#fff;}"#;
const XTERM_COLORS: &[(u8, u8, u8)] = &[
/*0*/ (0, 0, 0),
/*1*/ (128, 0, 0),
/*2*/ (0, 128, 0),
/*3*/ (128, 128, 0),
/*4*/ (0, 0, 128),
/*5*/ (128, 0, 128),
/*6*/ (0, 128, 128),
/*7*/ (192, 192, 192),
/*8*/ (128, 128, 128),
/*9*/ (255, 0, 0),
/*10*/ (0, 255, 0),
/*11*/ (255, 255, 0),
/*12*/ (0, 0, 255),
/*13*/ (255, 0, 255),
/*14*/ (0, 255, 255),
/*15*/ (255, 255, 255),
/*16*/ (0, 0, 0),
/*17*/ (0, 0, 95),
/*18*/ (0, 0, 135),
/*19*/ (0, 0, 175),
/*20*/ (0, 0, 215),
/*21*/ (0, 0, 255),
/*22*/ (0, 95, 0),
/*23*/ (0, 95, 95),
/*24*/ (0, 95, 135),
/*25*/ (0, 95, 175),
/*26*/ (0, 95, 215),
/*27*/ (0, 95, 255),
/*28*/ (0, 135, 0),
/*29*/ (0, 135, 95),
/*30*/ (0, 135, 135),
/*31*/ (0, 135, 175),
/*32*/ (0, 135, 215),
/*33*/ (0, 135, 255),
/*34*/ (0, 175, 0),
/*35*/ (0, 175, 95),
/*36*/ (0, 175, 135),
/*37*/ (0, 175, 175),
/*38*/ (0, 175, 215),
/*39*/ (0, 175, 255),
/*40*/ (0, 215, 0),
/*41*/ (0, 215, 95),
/*42*/ (0, 215, 135),
/*43*/ (0, 215, 175),
/*44*/ (0, 215, 215),
/*45*/ (0, 215, 255),
/*46*/ (0, 255, 0),
/*47*/ (0, 255, 95),
/*48*/ (0, 255, 135),
/*49*/ (0, 255, 175),
/*50*/ (0, 255, 215),
/*51*/ (0, 255, 255),
/*52*/ (95, 0, 0),
/*53*/ (95, 0, 95),
/*54*/ (95, 0, 135),
/*55*/ (95, 0, 175),
/*56*/ (95, 0, 215),
/*57*/ (95, 0, 255),
/*58*/ (95, 95, 0),
/*59*/ (95, 95, 95),
/*60*/ (95, 95, 135),
/*61*/ (95, 95, 175),
/*62*/ (95, 95, 215),
/*63*/ (95, 95, 255),
/*64*/ (95, 135, 0),
/*65*/ (95, 135, 95),
/*66*/ (95, 135, 135),
/*67*/ (95, 135, 175),
/*68*/ (95, 135, 215),
/*69*/ (95, 135, 255),
/*70*/ (95, 175, 0),
/*71*/ (95, 175, 95),
/*72*/ (95, 175, 135),
/*73*/ (95, 175, 175),
/*74*/ (95, 175, 215),
/*75*/ (95, 175, 255),
/*76*/ (95, 215, 0),
/*77*/ (95, 215, 95),
/*78*/ (95, 215, 135),
/*79*/ (95, 215, 175),
/*80*/ (95, 215, 215),
/*81*/ (95, 215, 255),
/*82*/ (95, 255, 0),
/*83*/ (95, 255, 95),
/*84*/ (95, 255, 135),
/*85*/ (95, 255, 175),
/*86*/ (95, 255, 215),
/*87*/ (95, 255, 255),
/*88*/ (135, 0, 0),
/*89*/ (135, 0, 95),
/*90*/ (135, 0, 135),
/*91*/ (135, 0, 175),
/*92*/ (135, 0, 215),
/*93*/ (135, 0, 255),
/*94*/ (135, 95, 0),
/*95*/ (135, 95, 95),
/*96*/ (135, 95, 135),
/*97*/ (135, 95, 175),
/*98*/ (135, 95, 215),
/*99*/ (135, 95, 255),
/*100*/ (135, 135, 0),
/*101*/ (135, 135, 95),
/*102*/ (135, 135, 135),
/*103*/ (135, 135, 175),
/*104*/ (135, 135, 215),
/*105*/ (135, 135, 255),
/*106*/ (135, 175, 0),
/*107*/ (135, 175, 95),
/*108*/ (135, 175, 135),
/*109*/ (135, 175, 175),
/*110*/ (135, 175, 215),
/*111*/ (135, 175, 255),
/*112*/ (135, 215, 0),
/*113*/ (135, 215, 95),
/*114*/ (135, 215, 135),
/*115*/ (135, 215, 175),
/*116*/ (135, 215, 215),
/*117*/ (135, 215, 255),
/*118*/ (135, 255, 0),
/*119*/ (135, 255, 95),
/*120*/ (135, 255, 135),
/*121*/ (135, 255, 175),
/*122*/ (135, 255, 215),
/*123*/ (135, 255, 255),
/*124*/ (175, 0, 0),
/*125*/ (175, 0, 95),
/*126*/ (175, 0, 135),
/*127*/ (175, 0, 175),
/*128*/ (175, 0, 215),
/*129*/ (175, 0, 255),
/*130*/ (175, 95, 0),
/*131*/ (175, 95, 95),
/*132*/ (175, 95, 135),
/*133*/ (175, 95, 175),
/*134*/ (175, 95, 215),
/*135*/ (175, 95, 255),
/*136*/ (175, 135, 0),
/*137*/ (175, 135, 95),
/*138*/ (175, 135, 135),
/*139*/ (175, 135, 175),
/*140*/ (175, 135, 215),
/*141*/ (175, 135, 255),
/*142*/ (175, 175, 0),
/*143*/ (175, 175, 95),
/*144*/ (175, 175, 135),
/*145*/ (175, 175, 175),
/*146*/ (175, 175, 215),
/*147*/ (175, 175, 255),
/*148*/ (175, 215, 0),
/*149*/ (175, 215, 95),
/*150*/ (175, 215, 135),
/*151*/ (175, 215, 175),
/*152*/ (175, 215, 215),
/*153*/ (175, 215, 255),
/*154*/ (175, 255, 0),
/*155*/ (175, 255, 95),
/*156*/ (175, 255, 135),
/*157*/ (175, 255, 175),
/*158*/ (175, 255, 215),
/*159*/ (175, 255, 255),
/*160*/ (215, 0, 0),
/*161*/ (215, 0, 95),
/*162*/ (215, 0, 135),
/*163*/ (215, 0, 175),
/*164*/ (215, 0, 215),
/*165*/ (215, 0, 255),
/*166*/ (215, 95, 0),
/*167*/ (215, 95, 95),
/*168*/ (215, 95, 135),
/*169*/ (215, 95, 175),
/*170*/ (215, 95, 215),
/*171*/ (215, 95, 255),
/*172*/ (215, 135, 0),
/*173*/ (215, 135, 95),
/*174*/ (215, 135, 135),
/*175*/ (215, 135, 175),
/*176*/ (215, 135, 215),
/*177*/ (215, 135, 255),
/*178*/ (215, 175, 0),
/*179*/ (215, 175, 95),
/*180*/ (215, 175, 135),
/*181*/ (215, 175, 175),
/*182*/ (215, 175, 215),
/*183*/ (215, 175, 255),
/*184*/ (215, 215, 0),
/*185*/ (215, 215, 95),
/*186*/ (215, 215, 135),
/*187*/ (215, 215, 175),
/*188*/ (215, 215, 215),
/*189*/ (215, 215, 255),
/*190*/ (215, 255, 0),
/*191*/ (215, 255, 95),
/*192*/ (215, 255, 135),
/*193*/ (215, 255, 175),
/*194*/ (215, 255, 215),
/*195*/ (215, 255, 255),
/*196*/ (255, 0, 0),
/*197*/ (255, 0, 95),
/*198*/ (255, 0, 135),
/*199*/ (255, 0, 175),
/*200*/ (255, 0, 215),
/*201*/ (255, 0, 255),
/*202*/ (255, 95, 0),
/*203*/ (255, 95, 95),
/*204*/ (255, 95, 135),
/*205*/ (255, 95, 175),
/*206*/ (255, 95, 215),
/*207*/ (255, 95, 255),
/*208*/ (255, 135, 0),
/*209*/ (255, 135, 95),
/*210*/ (255, 135, 135),
/*211*/ (255, 135, 175),
/*212*/ (255, 135, 215),
/*213*/ (255, 135, 255),
/*214*/ (255, 175, 0),
/*215*/ (255, 175, 95),
/*216*/ (255, 175, 135),
/*217*/ (255, 175, 175),
/*218*/ (255, 175, 215),
/*219*/ (255, 175, 255),
/*220*/ (255, 215, 0),
/*221*/ (255, 215, 95),
/*222*/ (255, 215, 135),
/*223*/ (255, 215, 175),
/*224*/ (255, 215, 215),
/*225*/ (255, 215, 255),
/*226*/ (255, 255, 0),
/*227*/ (255, 255, 95),
/*228*/ (255, 255, 135),
/*229*/ (255, 255, 175),
/*230*/ (255, 255, 215),
/*231*/ (255, 255, 255),
/*232*/ (8, 8, 8),
/*233*/ (18, 18, 18),
/*234*/ (28, 28, 28),
/*235*/ (38, 38, 38),
/*236*/ (48, 48, 48),
/*237*/ (58, 58, 58),
/*238*/ (68, 68, 68),
/*239*/ (78, 78, 78),
/*240*/ (88, 88, 88),
/*241*/ (98, 98, 98),
/*242*/ (108, 108, 108),
/*243*/ (118, 118, 118),
/*244*/ (128, 128, 128),
/*245*/ (138, 138, 138),
/*246*/ (148, 148, 148),
/*247*/ (158, 158, 158),
/*248*/ (168, 168, 168),
/*249*/ (178, 178, 178),
/*250*/ (188, 188, 188),
/*251*/ (198, 198, 198),
/*252*/ (208, 208, 208),
/*253*/ (218, 218, 218),
/*254*/ (228, 228, 228),
/*255*/ (238, 238, 238),
/* 0 */ (0, 0, 0),
/* 1 */ (128, 0, 0),
/* 2 */ (0, 128, 0),
/* 3 */ (128, 128, 0),
/* 4 */ (0, 0, 128),
/* 5 */ (128, 0, 128),
/* 6 */ (0, 128, 128),
/* 7 */ (192, 192, 192),
/* 8 */ (128, 128, 128),
/* 9 */ (255, 0, 0),
/* 10 */ (0, 255, 0),
/* 11 */ (255, 255, 0),
/* 12 */ (0, 0, 255),
/* 13 */ (255, 0, 255),
/* 14 */ (0, 255, 255),
/* 15 */ (255, 255, 255),
/* 16 */ (0, 0, 0),
/* 17 */ (0, 0, 95),
/* 18 */ (0, 0, 135),
/* 19 */ (0, 0, 175),
/* 20 */ (0, 0, 215),
/* 21 */ (0, 0, 255),
/* 22 */ (0, 95, 0),
/* 23 */ (0, 95, 95),
/* 24 */ (0, 95, 135),
/* 25 */ (0, 95, 175),
/* 26 */ (0, 95, 215),
/* 27 */ (0, 95, 255),
/* 28 */ (0, 135, 0),
/* 29 */ (0, 135, 95),
/* 30 */ (0, 135, 135),
/* 31 */ (0, 135, 175),
/* 32 */ (0, 135, 215),
/* 33 */ (0, 135, 255),
/* 34 */ (0, 175, 0),
/* 35 */ (0, 175, 95),
/* 36 */ (0, 175, 135),
/* 37 */ (0, 175, 175),
/* 38 */ (0, 175, 215),
/* 39 */ (0, 175, 255),
/* 40 */ (0, 215, 0),
/* 41 */ (0, 215, 95),
/* 42 */ (0, 215, 135),
/* 43 */ (0, 215, 175),
/* 44 */ (0, 215, 215),
/* 45 */ (0, 215, 255),
/* 46 */ (0, 255, 0),
/* 47 */ (0, 255, 95),
/* 48 */ (0, 255, 135),
/* 49 */ (0, 255, 175),
/* 50 */ (0, 255, 215),
/* 51 */ (0, 255, 255),
/* 52 */ (95, 0, 0),
/* 53 */ (95, 0, 95),
/* 54 */ (95, 0, 135),
/* 55 */ (95, 0, 175),
/* 56 */ (95, 0, 215),
/* 57 */ (95, 0, 255),
/* 58 */ (95, 95, 0),
/* 59 */ (95, 95, 95),
/* 60 */ (95, 95, 135),
/* 61 */ (95, 95, 175),
/* 62 */ (95, 95, 215),
/* 63 */ (95, 95, 255),
/* 64 */ (95, 135, 0),
/* 65 */ (95, 135, 95),
/* 66 */ (95, 135, 135),
/* 67 */ (95, 135, 175),
/* 68 */ (95, 135, 215),
/* 69 */ (95, 135, 255),
/* 70 */ (95, 175, 0),
/* 71 */ (95, 175, 95),
/* 72 */ (95, 175, 135),
/* 73 */ (95, 175, 175),
/* 74 */ (95, 175, 215),
/* 75 */ (95, 175, 255),
/* 76 */ (95, 215, 0),
/* 77 */ (95, 215, 95),
/* 78 */ (95, 215, 135),
/* 79 */ (95, 215, 175),
/* 80 */ (95, 215, 215),
/* 81 */ (95, 215, 255),
/* 82 */ (95, 255, 0),
/* 83 */ (95, 255, 95),
/* 84 */ (95, 255, 135),
/* 85 */ (95, 255, 175),
/* 86 */ (95, 255, 215),
/* 87 */ (95, 255, 255),
/* 88 */ (135, 0, 0),
/* 89 */ (135, 0, 95),
/* 90 */ (135, 0, 135),
/* 91 */ (135, 0, 175),
/* 92 */ (135, 0, 215),
/* 93 */ (135, 0, 255),
/* 94 */ (135, 95, 0),
/* 95 */ (135, 95, 95),
/* 96 */ (135, 95, 135),
/* 97 */ (135, 95, 175),
/* 98 */ (135, 95, 215),
/* 99 */ (135, 95, 255),
/* 100 */ (135, 135, 0),
/* 101 */ (135, 135, 95),
/* 102 */ (135, 135, 135),
/* 103 */ (135, 135, 175),
/* 104 */ (135, 135, 215),
/* 105 */ (135, 135, 255),
/* 106 */ (135, 175, 0),
/* 107 */ (135, 175, 95),
/* 108 */ (135, 175, 135),
/* 109 */ (135, 175, 175),
/* 110 */ (135, 175, 215),
/* 111 */ (135, 175, 255),
/* 112 */ (135, 215, 0),
/* 113 */ (135, 215, 95),
/* 114 */ (135, 215, 135),
/* 115 */ (135, 215, 175),
/* 116 */ (135, 215, 215),
/* 117 */ (135, 215, 255),
/* 118 */ (135, 255, 0),
/* 119 */ (135, 255, 95),
/* 120 */ (135, 255, 135),
/* 121 */ (135, 255, 175),
/* 122 */ (135, 255, 215),
/* 123 */ (135, 255, 255),
/* 124 */ (175, 0, 0),
/* 125 */ (175, 0, 95),
/* 126 */ (175, 0, 135),
/* 127 */ (175, 0, 175),
/* 128 */ (175, 0, 215),
/* 129 */ (175, 0, 255),
/* 130 */ (175, 95, 0),
/* 131 */ (175, 95, 95),
/* 132 */ (175, 95, 135),
/* 133 */ (175, 95, 175),
/* 134 */ (175, 95, 215),
/* 135 */ (175, 95, 255),
/* 136 */ (175, 135, 0),
/* 137 */ (175, 135, 95),
/* 138 */ (175, 135, 135),
/* 139 */ (175, 135, 175),
/* 140 */ (175, 135, 215),
/* 141 */ (175, 135, 255),
/* 142 */ (175, 175, 0),
/* 143 */ (175, 175, 95),
/* 144 */ (175, 175, 135),
/* 145 */ (175, 175, 175),
/* 146 */ (175, 175, 215),
/* 147 */ (175, 175, 255),
/* 148 */ (175, 215, 0),
/* 149 */ (175, 215, 95),
/* 150 */ (175, 215, 135),
/* 151 */ (175, 215, 175),
/* 152 */ (175, 215, 215),
/* 153 */ (175, 215, 255),
/* 154 */ (175, 255, 0),
/* 155 */ (175, 255, 95),
/* 156 */ (175, 255, 135),
/* 157 */ (175, 255, 175),
/* 158 */ (175, 255, 215),
/* 159 */ (175, 255, 255),
/* 160 */ (215, 0, 0),
/* 161 */ (215, 0, 95),
/* 162 */ (215, 0, 135),
/* 163 */ (215, 0, 175),
/* 164 */ (215, 0, 215),
/* 165 */ (215, 0, 255),
/* 166 */ (215, 95, 0),
/* 167 */ (215, 95, 95),
/* 168 */ (215, 95, 135),
/* 169 */ (215, 95, 175),
/* 170 */ (215, 95, 215),
/* 171 */ (215, 95, 255),
/* 172 */ (215, 135, 0),
/* 173 */ (215, 135, 95),
/* 174 */ (215, 135, 135),
/* 175 */ (215, 135, 175),
/* 176 */ (215, 135, 215),
/* 177 */ (215, 135, 255),
/* 178 */ (215, 175, 0),
/* 179 */ (215, 175, 95),
/* 180 */ (215, 175, 135),
/* 181 */ (215, 175, 175),
/* 182 */ (215, 175, 215),
/* 183 */ (215, 175, 255),
/* 184 */ (215, 215, 0),
/* 185 */ (215, 215, 95),
/* 186 */ (215, 215, 135),
/* 187 */ (215, 215, 175),
/* 188 */ (215, 215, 215),
/* 189 */ (215, 215, 255),
/* 190 */ (215, 255, 0),
/* 191 */ (215, 255, 95),
/* 192 */ (215, 255, 135),
/* 193 */ (215, 255, 175),
/* 194 */ (215, 255, 215),
/* 195 */ (215, 255, 255),
/* 196 */ (255, 0, 0),
/* 197 */ (255, 0, 95),
/* 198 */ (255, 0, 135),
/* 199 */ (255, 0, 175),
/* 200 */ (255, 0, 215),
/* 201 */ (255, 0, 255),
/* 202 */ (255, 95, 0),
/* 203 */ (255, 95, 95),
/* 204 */ (255, 95, 135),
/* 205 */ (255, 95, 175),
/* 206 */ (255, 95, 215),
/* 207 */ (255, 95, 255),
/* 208 */ (255, 135, 0),
/* 209 */ (255, 135, 95),
/* 210 */ (255, 135, 135),
/* 211 */ (255, 135, 175),
/* 212 */ (255, 135, 215),
/* 213 */ (255, 135, 255),
/* 214 */ (255, 175, 0),
/* 215 */ (255, 175, 95),
/* 216 */ (255, 175, 135),
/* 217 */ (255, 175, 175),
/* 218 */ (255, 175, 215),
/* 219 */ (255, 175, 255),
/* 220 */ (255, 215, 0),
/* 221 */ (255, 215, 95),
/* 222 */ (255, 215, 135),
/* 223 */ (255, 215, 175),
/* 224 */ (255, 215, 215),
/* 225 */ (255, 215, 255),
/* 226 */ (255, 255, 0),
/* 227 */ (255, 255, 95),
/* 228 */ (255, 255, 135),
/* 229 */ (255, 255, 175),
/* 230 */ (255, 255, 215),
/* 231 */ (255, 255, 255),
/* 232 */ (8, 8, 8),
/* 233 */ (18, 18, 18),
/* 234 */ (28, 28, 28),
/* 235 */ (38, 38, 38),
/* 236 */ (48, 48, 48),
/* 237 */ (58, 58, 58),
/* 238 */ (68, 68, 68),
/* 239 */ (78, 78, 78),
/* 240 */ (88, 88, 88),
/* 241 */ (98, 98, 98),
/* 242 */ (108, 108, 108),
/* 243 */ (118, 118, 118),
/* 244 */ (128, 128, 128),
/* 245 */ (138, 138, 138),
/* 246 */ (148, 148, 148),
/* 247 */ (158, 158, 158),
/* 248 */ (168, 168, 168),
/* 249 */ (178, 178, 178),
/* 250 */ (188, 188, 188),
/* 251 */ (198, 198, 198),
/* 252 */ (208, 208, 208),
/* 253 */ (218, 218, 218),
/* 254 */ (228, 228, 228),
/* 255 */ (238, 238, 238),
];

View File

@ -21,9 +21,10 @@
/*! Various useful components that can be used in a generic fashion.
*/
use super::*;
use text_processing::Reflow;
use super::*;
mod pager;
pub use self::pager::*;
@ -37,11 +38,11 @@ mod dialogs;
pub use self::dialogs::*;
mod tables;
pub use self::tables::*;
use crate::jobs::JobId;
use std::collections::HashSet;
pub use self::tables::*;
use crate::jobs::JobId;
#[derive(Default, Debug, Clone)]
pub struct SearchPattern {
pattern: String,
@ -943,12 +944,13 @@ impl Component for Tabbed {
));
}
/* If children are dirty but self isn't and the shortcuts panel is visible, it will get
* overwritten. */
/* If children are dirty but self isn't and the shortcuts panel is visible,
* it will get overwritten. */
let must_redraw_shortcuts: bool = self.show_shortcuts && !self.dirty && self.is_dirty();
/* children should be drawn after the shortcuts/help panel lest they overwrite the panel on
* the grid. the drawing order is determined by the dirty_areas queue which is LIFO */
/* children should be drawn after the shortcuts/help panel lest they
* overwrite the panel on the grid. the drawing order is determined
* by the dirty_areas queue which is LIFO */
if self.children.len() > 1 {
self.draw_tabs(
grid,

View File

@ -40,9 +40,10 @@ enum SelectorCursor {
/// Shows a little window with options for user to select.
///
/// Instantiate with Selector::new(). Set single_only to true if user should only choose one of the
/// options. After passing input events to this component, check Selector::is_done to see if the
/// user has finalised their choices. Collect the choices by consuming the Selector with
/// Instantiate with Selector::new(). Set single_only to true if user should
/// only choose one of the options. After passing input events to this
/// component, check Selector::is_done to see if the user has finalised their
/// choices. Collect the choices by consuming the Selector with
/// Selector::collect()
pub struct Selector<T: 'static + PartialEq + Debug + Clone + Sync + Send, F: 'static + Sync + Send>
{

Some files were not shown because too many files have changed in this diff Show More