melib: add utils::{futures, random}

pull/231/head
Manos Pitsidianakis 2023-06-18 22:28:40 +03:00
parent 02e86d1fad
commit 5699baecfb
Signed by: Manos Pitsidianakis
GPG Key ID: 7729C7707F7E09D0
46 changed files with 424 additions and 293 deletions

View File

@ -28,7 +28,7 @@ use std::{collections::HashMap, ops::Deref};
use uuid::Uuid;
use crate::{
use crate::utils::{
datetime::{self, UnixTimestamp},
parsec::Parser,
};

View File

@ -24,7 +24,7 @@
use std::collections::VecDeque;
use super::*;
use crate::parsec::{is_not, map_res, match_literal_anycase, prefix, Parser};
use crate::utils::parsec::{is_not, map_res, match_literal_anycase, prefix, Parser};
//alias <nickname> [ <long name> ] <address>
// From mutt doc:

View File

@ -32,7 +32,7 @@ use std::{collections::HashMap, convert::TryInto};
use super::*;
use crate::{
error::{Error, Result},
parsec::{match_literal_anycase, one_or_more, peek, prefix, take_until, Parser},
utils::parsec::{match_literal_anycase, one_or_more, peek, prefix, take_until, Parser},
};
/* Supported vcard versions */
@ -222,8 +222,9 @@ impl<V: VCardVersion> TryInto<Card> for VCard<V> {
T102200Z
T102200-0800
*/
card.birthday = crate::datetime::timestamp_from_string(val.value.as_str(), "%Y%m%d\0")
.unwrap_or_default();
card.birthday =
crate::utils::datetime::timestamp_from_string(val.value.as_str(), "%Y%m%d\0")
.unwrap_or_default();
}
if let Some(val) = self.0.remove("EMAIL") {
card.set_email(val.value);

View File

@ -65,9 +65,9 @@ use crate::{
},
collection::Collection,
conf::AccountSettings,
connections::timeout,
email::{parser::BytesExt, *},
error::{Error, Result, ResultIntoError},
utils::futures::timeout,
};
pub type ImapNum = usize;

View File

@ -106,17 +106,17 @@ pub use sqlite3_m::*;
#[cfg(feature = "sqlite3")]
mod sqlite3_m {
use super::*;
use crate::sqlite3::{
use crate::utils::sqlite3::{
self,
rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput},
DatabaseDescription,
Connection, DatabaseDescription,
};
type Sqlite3UID = i32;
#[derive(Debug)]
pub struct Sqlite3Cache {
connection: crate::sqlite3::Connection,
connection: Connection,
loaded_mailboxes: BTreeSet<MailboxHash>,
uid_store: Arc<UIDStore>,
}

View File

@ -21,10 +21,13 @@
use super::protocol_parser::{ImapLineSplit, ImapResponse, RequiredResponses, SelectResponse};
use crate::{
backends::{MailboxHash, RefreshEvent},
connections::{lookup_ipv4, timeout, Connection},
backends::{BackendEvent, MailboxHash, RefreshEvent},
email::parser::BytesExt,
error::*,
utils::{
connections::{lookup_ipv4, Connection},
futures::timeout,
},
LogLevel,
};
extern crate native_tls;
@ -162,7 +165,7 @@ impl ImapStream {
let stream = if server_conf.use_tls {
(uid_store.event_consumer)(
uid_store.account_hash,
crate::backends::BackendEvent::AccountStateChange {
BackendEvent::AccountStateChange {
message: "Establishing TLS connection.".into(),
},
);
@ -172,9 +175,7 @@ impl ImapStream {
}
let connector = connector
.build()
.chain_err_kind(crate::error::ErrorKind::Network(
crate::error::NetworkErrorKind::InvalidTLSConnection,
))?;
.chain_err_kind(ErrorKind::Network(NetworkErrorKind::InvalidTLSConnection))?;
let addr = lookup_ipv4(path, server_conf.server_port)?;
@ -263,8 +264,8 @@ impl ImapStream {
midhandshake_stream = Some(stream);
}
p => {
p.chain_err_kind(crate::error::ErrorKind::Network(
crate::error::NetworkErrorKind::InvalidTLSConnection,
p.chain_err_kind(ErrorKind::Network(
NetworkErrorKind::InvalidTLSConnection,
))?;
}
}
@ -276,11 +277,7 @@ impl ImapStream {
.chain_err_summary(|| format!("Could not initiate TLS negotiation to {}.", path))?
}
} else {
let addr = if let Ok(a) = lookup_ipv4(path, server_conf.server_port) {
a
} else {
return Err(Error::new(format!("Could not lookup address {}", &path)));
};
let addr = lookup_ipv4(path, server_conf.server_port)?;
AsyncWrapper::new(Connection::Tcp(
if let Some(timeout) = server_conf.timeout {
TcpStream::connect_timeout(&addr, timeout)?
@ -322,7 +319,7 @@ impl ImapStream {
(uid_store.event_consumer)(
uid_store.account_hash,
crate::backends::BackendEvent::AccountStateChange {
BackendEvent::AccountStateChange {
message: "Negotiating server capabilities.".into(),
},
);
@ -344,7 +341,7 @@ impl ImapStream {
&server_conf.server_hostname,
String::from_utf8_lossy(&res)
))
.set_kind(ErrorKind::Bug));
.set_kind(ErrorKind::ProtocolError));
}
let capabilities = capabilities.unwrap();
@ -355,7 +352,8 @@ impl ImapStream {
return Err(Error::new(format!(
"Could not connect to {}: server is not IMAP4rev1 compliant",
&server_conf.server_hostname
)));
))
.set_kind(ErrorKind::ProtocolNotSupported));
} else if capabilities
.iter()
.any(|cap| cap.eq_ignore_ascii_case(b"LOGINDISABLED"))
@ -364,12 +362,12 @@ impl ImapStream {
"Could not connect to {}: server does not accept logins [LOGINDISABLED]",
&server_conf.server_hostname
))
.set_err_kind(crate::error::ErrorKind::Authentication));
.set_err_kind(ErrorKind::Authentication));
}
(uid_store.event_consumer)(
uid_store.account_hash,
crate::backends::BackendEvent::AccountStateChange {
BackendEvent::AccountStateChange {
message: "Attempting authentication.".into(),
},
);
@ -390,10 +388,14 @@ impl ImapStream {
.map(|capability| String::from_utf8_lossy(capability).to_string())
.collect::<Vec<String>>()
.join(" ")
)));
))
.set_err_kind(ErrorKind::Authentication));
}
let xoauth2 = base64::decode(&server_conf.server_password)
.map_err(|_| Error::new("Bad XOAUTH2 in config"))?;
.chain_err_summary(|| {
"Could not decode `server_password` from base64. Is the value correct?"
})
.chain_err_kind(ErrorKind::Configuration)?;
// TODO(#222): Improve this as soon as imap-codec supports XOAUTH2.
ret.send_command(CommandBody::authenticate(
AuthMechanism::Other(
@ -404,8 +406,10 @@ impl ImapStream {
.await?;
}
_ => {
let username = AString::try_from(server_conf.server_username.as_str())?;
let password = AString::try_from(server_conf.server_password.as_str())?;
let username = AString::try_from(server_conf.server_username.as_str())
.chain_err_kind(ErrorKind::Bug)?;
let password = AString::try_from(server_conf.server_password.as_str())
.chain_err_kind(ErrorKind::Bug)?;
ret.send_command(CommandBody::Login {
username,
@ -435,7 +439,7 @@ impl ImapStream {
"Could not connect. Server replied with '{}'",
String::from_utf8_lossy(l[tag_start.len()..].trim())
))
.set_err_kind(crate::error::ErrorKind::Authentication));
.set_err_kind(ErrorKind::Authentication));
}
should_break = true;
}
@ -448,7 +452,6 @@ impl ImapStream {
if capabilities.is_none() {
/* sending CAPABILITY after LOGIN automatically is an RFC recommendation, so
* check for lazy servers */
drop(capabilities);
ret.send_command(CommandBody::Capability).await?;
ret.read_response(&mut res).await.unwrap();
let capabilities = protocol_parser::capabilities(&res)?.1;
@ -827,7 +830,7 @@ impl ImapConnection {
);
(self.uid_store.event_consumer)(
self.uid_store.account_hash,
crate::backends::BackendEvent::Notice {
BackendEvent::Notice {
description: response_code.to_string(),
content: None,
level: LogLevel::ERROR,
@ -845,7 +848,7 @@ impl ImapConnection {
);
(self.uid_store.event_consumer)(
self.uid_store.account_hash,
crate::backends::BackendEvent::Notice {
BackendEvent::Notice {
description: response_code.to_string(),
content: None,
level: LogLevel::ERROR,
@ -974,7 +977,7 @@ impl ImapConnection {
"Trying to select a \\NoSelect mailbox: {}",
&imap_path
))
.set_kind(crate::error::ErrorKind::Bug));
.set_kind(ErrorKind::Bug));
}
self.send_command(CommandBody::select(imap_path.as_str())?)
.await?;
@ -1004,7 +1007,7 @@ impl ImapConnection {
}) {
(self.uid_store.event_consumer)(
self.uid_store.account_hash,
crate::backends::BackendEvent::from(err),
BackendEvent::from(err),
);
}
}
@ -1061,7 +1064,7 @@ impl ImapConnection {
"Trying to examine a \\NoSelect mailbox: {}",
&imap_path
))
.set_kind(crate::error::ErrorKind::Bug));
.set_kind(ErrorKind::Bug));
}
self.send_command(CommandBody::examine(imap_path.as_str())?)
.await?;
@ -1126,10 +1129,7 @@ impl ImapConnection {
}
pub fn add_refresh_event(&mut self, ev: RefreshEvent) {
(self.uid_store.event_consumer)(
self.uid_store.account_hash,
crate::backends::BackendEvent::Refresh(ev),
);
(self.uid_store.event_consumer)(self.uid_store.account_hash, BackendEvent::Refresh(ev));
}
async fn create_uid_msn_cache(

View File

@ -24,8 +24,8 @@
use std::collections::VecDeque;
use crate::{
datetime::{formats::IMAP_DATE, timestamp_to_string},
search::*,
utils::datetime::{formats::IMAP_DATE, timestamp_to_string},
};
mod private {
@ -256,7 +256,7 @@ impl ToImapSearch for Query {
#[cfg(test)]
mod tests {
use super::*;
use crate::parsec::Parser;
use crate::utils::parsec::Parser;
#[test]
fn test_imap_query_search() {

View File

@ -34,9 +34,9 @@ use serde_json::Value;
use crate::{
backends::*,
conf::AccountSettings,
connections::timeout,
email::*,
error::{Error, Result},
utils::futures::{sleep, timeout},
Collection,
};
@ -344,7 +344,7 @@ impl MailBackend for JmapType {
conn.email_changes(mailbox_hash).await?;
}
}
crate::connections::sleep(Duration::from_secs(60)).await;
sleep(Duration::from_secs(60)).await;
}
}))
}

View File

@ -29,6 +29,7 @@ use super::*;
use crate::{
backends::jmap::rfc8620::bool_false,
email::address::{Address, MailboxAddress},
utils::datetime,
};
mod import;
@ -265,8 +266,7 @@ impl std::convert::From<EmailObject> for crate::Envelope {
env.set_datetime(d);
}
if let Some(ref mut sent_at) = t.sent_at {
let unix =
crate::datetime::rfc3339_to_timestamp(sent_at.as_bytes().to_vec()).unwrap_or(0);
let unix = datetime::rfc3339_to_timestamp(sent_at.as_bytes().to_vec()).unwrap_or(0);
env.set_datetime(unix);
env.set_date(std::mem::replace(sent_at, String::new()).as_bytes());
}
@ -588,10 +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::{formats::RFC3339_DATE, timestamp_to_string},
search::Query::*,
};
use datetime::{formats::RFC3339_DATE, timestamp_to_string};
use crate::search::Query::*;
match q {
Subject(t) => {
*f = Filter::Condition(EmailFilterCondition::new().subject(t.clone()).into());

View File

@ -40,7 +40,7 @@ use crate::{
backends::*,
email::Flag,
error::{Error, Result},
shellexpand::ShellExpandTrait,
utils::shellexpand::ShellExpandTrait,
};
/// `BackendOp` implementor for Maildir

View File

@ -32,7 +32,7 @@ use crate::{
conf::AccountSettings,
email::{Envelope, EnvelopeHash, Flag},
error::{Error, ErrorKind, Result},
shellexpand::ShellExpandTrait,
utils::shellexpand::ShellExpandTrait,
Collection,
};

View File

@ -146,7 +146,7 @@ use crate::{
email::{parser::BytesExt, *},
error::{Error, ErrorKind, Result},
get_path_hash,
shellexpand::ShellExpandTrait,
utils::shellexpand::ShellExpandTrait,
};
extern crate notify;

View File

@ -20,7 +20,7 @@
*/
use super::*;
use crate::datetime;
use crate::utils::datetime;
impl MboxFormat {
pub fn append(

View File

@ -52,9 +52,9 @@ use futures::{lock::Mutex as FutureMutex, stream::Stream};
use crate::{
backends::*,
conf::AccountSettings,
connections::timeout,
email::*,
error::{Error, Result, ResultIntoError},
utils::futures::timeout,
Collection,
};
pub type UID = usize;
@ -286,8 +286,11 @@ impl MailBackend for NntpType {
let mut conn = timeout(Some(Duration::from_secs(60 * 16)), connection.lock()).await?;
if let Some(mut latest_article) = latest_article {
let timestamp = latest_article - 10 * 60;
let datetime_str =
crate::datetime::timestamp_to_string(timestamp, Some("%Y%m%d %H%M%S"), true);
let datetime_str = crate::utils::datetime::timestamp_to_string(
timestamp,
Some("%Y%m%d %H%M%S"),
true,
);
if newnews_support {
conn.send_command(

View File

@ -21,10 +21,10 @@
use crate::{
backends::{BackendMailbox, MailboxHash},
connections::{lookup_ipv4, Connection},
email::parser::BytesExt,
error::*,
log,
utils::connections::{lookup_ipv4, Connection},
};
extern crate native_tls;
use std::{collections::HashSet, future::Future, pin::Pin, sync::Arc, time::Instant};

View File

@ -35,7 +35,7 @@ use crate::{
conf::AccountSettings,
email::{Envelope, EnvelopeHash, Flag},
error::{Error, Result},
shellexpand::ShellExpandTrait,
utils::shellexpand::ShellExpandTrait,
Collection,
};

View File

@ -81,7 +81,7 @@ impl<'m> Message<'m> {
unsafe { CStr::from_ptr(msg_id) }
}
pub fn date(&self) -> crate::datetime::UnixTimestamp {
pub fn date(&self) -> crate::UnixTimestamp {
(unsafe { call!(self.lib, notmuch_message_get_date)(self.message) }) as u64
}

View File

@ -34,7 +34,7 @@ impl<'q> Thread<'q> {
ThreadHash::from(c_str.to_bytes())
}
pub fn date(&self) -> crate::datetime::UnixTimestamp {
pub fn date(&self) -> crate::UnixTimestamp {
(unsafe { call!(self.lib, notmuch_thread_get_newest_date)(self.ptr) }) as u64
}

View File

@ -122,11 +122,10 @@ use imap_codec::{
use smallvec::SmallVec;
use crate::{
datetime::UnixTimestamp,
error::{Error, Result},
parser::BytesExt,
thread::ThreadNodeHash,
TagHash,
TagHash, UnixTimestamp,
};
#[cfg(feature = "imap_backend")]

View File

@ -33,12 +33,11 @@ use xdg_utils::query_mime_info;
use super::*;
use crate::{
datetime,
email::{
attachment_types::{Charset, ContentTransferEncoding, ContentType, MultipartType},
attachments::AttachmentBuilder,
},
shellexpand::ShellExpandTrait,
utils::{datetime, shellexpand::ShellExpandTrait},
};
pub mod mime;

View File

@ -19,31 +19,7 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use std::{char, fs::File, io::prelude::*, time::SystemTime};
fn random_u64() -> u64 {
let mut f = File::open("/dev/urandom").unwrap();
let mut buffer = [0; 8];
// read exactly 10 bytes
f.read_exact(&mut buffer).unwrap();
u64::from(buffer[0])
| (u64::from(buffer[1]) << 8)
| (u64::from(buffer[2]) << 16)
| (u64::from(buffer[3]) << 24)
| (u64::from(buffer[4]) << 32)
| (u64::from(buffer[5]) << 40)
| (u64::from(buffer[6]) << 48)
| (u64::from(buffer[7]) << 56)
}
fn clock() -> u64 {
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs()
}
use crate::utils::random::{clock, random_u64};
fn base36(mut m: u64) -> String {
let mut stack = Vec::with_capacity(32);

View File

@ -29,7 +29,7 @@ use std::convert::TryFrom;
use super::*;
use crate::{
email::headers::HeaderMap,
percent_encoding::{AsciiSet, CONTROLS},
utils::percent_encoding::{AsciiSet, CONTROLS},
};
#[derive(Debug, Clone, Eq, PartialEq)]

View File

@ -42,8 +42,7 @@ use crate::{
mailto::Mailto,
},
error::{Error, Result, ResultIntoError},
html_escape::HtmlEntity,
percent_encoding::percent_decode,
utils::{html_escape::HtmlEntity, percent_encoding::percent_decode},
};
macro_rules! to_str {
@ -330,7 +329,7 @@ pub fn mail(input: &[u8]) -> Result<(Vec<(&[u8], &[u8])>, &[u8])> {
pub mod dates {
/*! Date values in headers */
use super::{generic::*, *};
use crate::datetime::UnixTimestamp;
use crate::utils::datetime::UnixTimestamp;
fn take_n_digits(n: usize) -> impl Fn(&[u8]) -> IResult<&[u8], &[u8]> {
move |input: &[u8]| {
@ -451,7 +450,7 @@ pub mod dates {
accum.extend_from_slice(b" ");
accum.extend_from_slice(sign);
accum.extend_from_slice(zone);
match crate::datetime::rfc822_to_timestamp(accum.to_vec()) {
match crate::utils::datetime::rfc822_to_timestamp(accum.to_vec()) {
Ok(t) => Ok((input, t)),
Err(_err) => Err(nom::Err::Error(
(
@ -513,7 +512,7 @@ pub mod dates {
accum.extend_from_slice(sign);
accum.extend_from_slice(zone);
}
match crate::datetime::rfc822_to_timestamp(accum.to_vec()) {
match crate::utils::datetime::rfc822_to_timestamp(accum.to_vec()) {
Ok(t) => Ok((input, t)),
Err(_err) => Err(nom::Err::Error(
(
@ -582,7 +581,7 @@ pub mod dates {
Ok((input, ret))
}
pub fn rfc5322_date(input: &[u8]) -> Result<crate::datetime::UnixTimestamp> {
pub fn rfc5322_date(input: &[u8]) -> Result<crate::UnixTimestamp> {
date_time(input)
.or_else(|_| {
//let (_, mut parsed_result) = encodings::phrase(&eat_comments(input), false)?;
@ -616,7 +615,7 @@ pub mod dates {
parsed_result[pos] = b'+';
}
crate::datetime::rfc822_to_timestamp(parsed_result.trim())
crate::utils::datetime::rfc822_to_timestamp(parsed_result.trim())
*/
}

View File

@ -183,7 +183,7 @@ pub enum NetworkErrorKind {
}
impl NetworkErrorKind {
pub fn as_str(&self) -> &'static str {
pub const fn as_str(&self) -> &'static str {
use NetworkErrorKind::*;
match self {
None => "Network",
@ -242,6 +242,19 @@ impl NetworkErrorKind {
NetworkAuthenticationRequired => "Network Authentication Required",
}
}
/// Error kind means network is certainly down.
pub const fn is_network_down(&self) -> bool {
use NetworkErrorKind::*;
matches!(
self,
BadGateway
| ServiceUnavailable
| GatewayTimeout
| NetworkAuthenticationRequired
| ConnectionFailed
)
}
}
impl Default for NetworkErrorKind {
@ -312,6 +325,13 @@ pub enum ErrorKind {
External,
Authentication,
Configuration,
/// Protocol error.
///
/// `EPROTO 71 Protocol error`
ProtocolError,
/// Protocol is not supported.
/// It could be the wrong type or version.
ProtocolNotSupported,
Bug,
Network(NetworkErrorKind),
Timeout,
@ -332,6 +352,9 @@ impl fmt::Display for ErrorKind {
Self::Authentication => "Authentication",
Self::Bug => "Bug, please report this!",
Self::Network(ref inner) => inner.as_str(),
Self::ProtocolError => "Protocol error",
Self::ProtocolNotSupported =>
"Protocol is not supported. It could be the wrong type or version.",
Self::Timeout => "Timeout",
Self::OSError => "OS Error",
Self::Configuration => "Configuration",
@ -343,18 +366,29 @@ impl fmt::Display for ErrorKind {
}
}
macro_rules! is_variant {
($n:ident, $($var:tt)+) => {
#[inline]
pub fn $n(&self) -> bool {
matches!(self, Self::$($var)*)
}
};
}
impl ErrorKind {
pub fn is_network(&self) -> bool {
matches!(self, Self::Network(_))
}
pub fn is_timeout(&self) -> bool {
matches!(self, Self::Timeout)
}
pub fn is_authentication(&self) -> bool {
matches!(self, Self::Authentication)
}
is_variant! { is_authentication, Authentication }
is_variant! { is_bug, Bug }
is_variant! { is_configuration, Configuration }
is_variant! { is_external, External }
is_variant! { is_network, Network(_) }
is_variant! { is_network_down, Network(ref k) if k.is_network_down() }
is_variant! { is_not_implemented, NotImplemented }
is_variant! { is_not_supported, NotSupported }
is_variant! { is_oserror, OSError }
is_variant! { is_protocol_error, ProtocolError }
is_variant! { is_protocol_not_supported, ProtocolNotSupported }
is_variant! { is_timeout, Timeout }
is_variant! { is_value_error, ValueError }
}
#[derive(Debug, Clone)]
@ -709,3 +743,12 @@ impl<'a> From<&'a Error> for Error {
kind.clone()
}
}
impl From<base64::DecodeError> for Error {
#[inline]
fn from(kind: base64::DecodeError) -> Error {
Error::new("base64 decoding failed")
.set_source(Some(Arc::new(kind)))
.set_kind(ErrorKind::ValueError)
}
}

View File

@ -82,9 +82,10 @@ pub mod dbg {
#[cfg(feature = "unicode_algorithms")]
pub mod text_processing;
pub use utils::{datetime::UnixTimestamp, *};
pub use self::logging::{LogLevel, StderrLogger};
pub use utils::{
datetime::UnixTimestamp,
logging::{LogLevel, StderrLogger},
};
pub mod addressbook;
pub use addressbook::*;
@ -98,13 +99,13 @@ pub use conf::*;
pub mod email;
pub use email::*;
pub mod error;
pub use crate::error::*;
pub use error::*;
pub mod thread;
pub use thread::*;
pub mod search;
#[macro_use]
mod utils;
pub mod utils;
#[cfg(feature = "gpgme")]
pub mod gpgme;
@ -158,4 +159,4 @@ impl core::fmt::Display for Bytes {
}
}
pub use shellexpand::ShellExpandTrait;
pub use utils::shellexpand::ShellExpandTrait;

View File

@ -24,7 +24,7 @@ use std::{borrow::Cow, convert::TryFrom};
pub use query_parser::query;
use Query::*;
use crate::{
use crate::utils::{
datetime::{formats, UnixTimestamp},
parsec::*,
};
@ -152,9 +152,10 @@ pub mod query_parser {
fn date<'a>() -> impl Parser<'a, UnixTimestamp> {
move |input| {
literal().parse(input).and_then(|(next_input, result)| {
if let Ok((_, t)) =
crate::datetime::parse_timestamp_from_string(result, formats::RFC3339_DATE)
{
if let Ok((_, t)) = crate::utils::datetime::parse_timestamp_from_string(
result,
formats::RFC3339_DATE,
) {
Ok((next_input, t))
} else {
Err(next_input)
@ -368,8 +369,8 @@ pub mod query_parser {
/// # Invocation
/// ```
/// use melib::{
/// parsec::Parser,
/// search::{query, Query},
/// utils::parsec::Parser,
/// };
///
/// let input = "test";

View File

@ -19,7 +19,7 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use crate::parsec::*;
use crate::utils::parsec::*;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RuleBlock(pub Vec<Rule>);
@ -683,7 +683,7 @@ mod test {
parser::*, ActionCommand::*, AddressOperator::*, CharacterOperator::*, ConditionRule::*,
ControlCommand::*, IntegerOperator::*, MatchOperator::*, Rule::*, RuleBlock,
};
use crate::parsec::Parser;
use crate::utils::parsec::Parser;
#[test]
fn test_sieve_parse_strings() {

View File

@ -80,9 +80,9 @@ use smallvec::SmallVec;
use smol::{unblock, Async as AsyncWrapper};
use crate::{
connections::{lookup_ipv4, Connection},
email::{parser::BytesExt, Address, Envelope},
error::{Error, Result, ResultIntoError},
utils::connections::{lookup_ipv4, Connection},
};
/// Kind of server security (StartTLS/TLS/None) the client should attempt

View File

@ -34,8 +34,8 @@
*/
use crate::{
datetime::UnixTimestamp,
email::{address::StrBuild, parser::BytesExt, *},
UnixTimestamp,
};
mod iterators;
@ -1674,7 +1674,7 @@ fn save_graph(
let mut file = File::create(format!(
"/tmp/meli/threads/threads_{}.json",
crate::datetime::now()
crate::utils::datetime::now()
))
.unwrap();
file.write_all(s.as_bytes()).unwrap();

View File

@ -275,24 +275,3 @@ pub fn lookup_ipv4(host: &str, port: u16) -> crate::Result<std::net::SocketAddr>
),
)
}
use futures::future::{self, Either, Future};
pub async fn timeout<O>(dur: Option<Duration>, f: impl Future<Output = O>) -> crate::Result<O> {
futures::pin_mut!(f);
if let Some(dur) = dur {
match future::select(f, smol::Timer::after(dur)).await {
Either::Left((out, _)) => Ok(out),
Either::Right(_) => {
Err(crate::error::Error::new("Timed out.")
.set_kind(crate::error::ErrorKind::Timeout))
}
}
} else {
Ok(f.await)
}
}
pub async fn sleep(dur: Duration) {
smol::Timer::after(dur).await;
}

View File

@ -0,0 +1,43 @@
/*
* meli - melib library
*
* Copyright 2020 Manos Pitsidianakis
*
* This file is part of meli.
*
* meli is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* meli is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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::time::Duration;
use futures::future::{self, Either, Future};
pub async fn timeout<O>(dur: Option<Duration>, f: impl Future<Output = O>) -> crate::Result<O> {
futures::pin_mut!(f);
if let Some(dur) = dur {
match future::select(f, smol::Timer::after(dur)).await {
Either::Left((out, _)) => Ok(out),
Either::Right(_) => {
Err(crate::error::Error::new("Timed out.")
.set_kind(crate::error::ErrorKind::Timeout))
}
}
} else {
Ok(f.await)
}
}
pub async fn sleep(dur: Duration) {
smol::Timer::after(dur).await;
}

View File

@ -220,7 +220,7 @@ impl StderrLogger {
#[cfg(not(test))]
pub fn change_log_dest(&mut self, path: PathBuf) {
use crate::shellexpand::ShellExpandTrait;
use crate::utils::shellexpand::ShellExpandTrait;
let path = path.expand(); // expand shell stuff
let mut dest = self.dest.lock().unwrap();
@ -254,7 +254,7 @@ impl Log for StderrLogger {
) -> Option<()> {
writer
.write_all(
crate::datetime::timestamp_to_string(crate::datetime::now(), None, false)
super::datetime::timestamp_to_string(super::datetime::now(), None, false)
.as_bytes(),
)
.ok()?;

View File

@ -23,6 +23,8 @@
pub mod connections;
pub mod datetime;
pub mod futures;
pub mod random;
#[macro_use]
pub mod logging;
pub mod parsec;

View File

@ -23,7 +23,7 @@
use std::borrow::Cow;
use crate::datetime::{parse_timestamp_from_string, UnixTimestamp};
use crate::utils::datetime::{parse_timestamp_from_string, UnixTimestamp};
pub type Result<'a, Output> = std::result::Result<(&'a str, Output), &'a str>;
@ -445,8 +445,8 @@ pub fn is_not<'a>(slice: &'static [u8]) -> impl Parser<'a, &'a str> {
/// Try alternative parsers in order until one succeeds.
///
/// ```rust
/// # use melib::parsec::{Parser, quoted_slice, match_literal, alt, delimited, prefix};
///
/// # use melib::utils::parsec::{Parser, quoted_slice, match_literal, alt, delimited, prefix};
/// #
/// let parser = |input| {
/// alt([
/// delimited(match_literal("{"), quoted_slice(), match_literal("}")),
@ -456,9 +456,8 @@ pub fn is_not<'a>(slice: &'static [u8]) -> impl Parser<'a, &'a str> {
/// };
///
/// let input1: &str = "{\"quoted\"}";
/// let input2: &str = "[\"quoted\"]";
/// assert_eq!(Ok(("", "quoted")), parser.parse(input1));
///
/// let input2: &str = "[\"quoted\"]";
/// assert_eq!(Ok(("", "quoted")), parser.parse(input2));
/// ```
pub fn alt<'a, P, A, const N: usize>(parsers: [P; N]) -> impl Parser<'a, A>
@ -584,7 +583,7 @@ 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};
/// # use melib::utils::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(

View File

@ -0,0 +1,71 @@
/*
* meli - melib crate.
*
* Copyright 2017-2020 Manos Pitsidianakis
*
* This file is part of meli.
*
* meli is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* meli is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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::{fs::File, io::prelude::*, time::SystemTime};
const EXPECT: &str = "Could not open/read /dev/urandom";
pub fn random_u64() -> u64 {
let mut f = File::open("/dev/urandom").expect(EXPECT);
let mut buffer = [0; 8];
// read exactly 8 bytes
f.read_exact(&mut buffer).expect(EXPECT);
u64::from(buffer[0])
| (u64::from(buffer[1]) << 8)
| (u64::from(buffer[2]) << 16)
| (u64::from(buffer[3]) << 24)
| (u64::from(buffer[4]) << 32)
| (u64::from(buffer[5]) << 40)
| (u64::from(buffer[6]) << 48)
| (u64::from(buffer[7]) << 56)
}
pub fn random_u32() -> u32 {
let mut f = File::open("/dev/urandom").expect(EXPECT);
let mut buffer = [0; 4];
// read exactly 4 bytes
f.read_exact(&mut buffer).expect(EXPECT);
u32::from(buffer[0])
| (u32::from(buffer[1]) << 8)
| (u32::from(buffer[2]) << 16)
| (u32::from(buffer[3]) << 24)
}
pub fn random_u8() -> u8 {
let mut f = File::open("/dev/urandom").expect(EXPECT);
let mut buffer = [0; 1];
// read exactly 1 byte
f.read_exact(&mut buffer).expect(EXPECT);
buffer[0]
}
pub fn clock() -> u64 {
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs()
}

View File

@ -1005,107 +1005,6 @@ pub fn parse_command(input: &[u8]) -> Result<Action, Error> {
.map_err(|err| err.into())
}
#[test]
fn test_parser() {
let mut input = "sort".to_string();
macro_rules! match_input {
($input:expr) => {{
let mut sugg = Default::default();
let mut vec = vec![];
//print!("{}", $input);
for (_tags, _desc, tokens) in COMMAND_COMPLETION.iter() {
//println!("{:?}, {:?}, {:?}", _tags, _desc, tokens);
let m = tokens.matches(&mut $input.as_str(), &mut sugg);
if !m.is_empty() {
vec.push(tokens);
//print!("{:?} ", desc);
//println!(" result = {:#?}\n\n", m);
}
}
//println!("suggestions = {:#?}", sugg);
sugg.into_iter()
.map(|s| format!("{}{}", $input.as_str(), s.as_str()))
.collect::<HashSet<String>>()
}};
}
assert_eq!(
&match_input!(input),
&IntoIterator::into_iter(["sort date".to_string(), "sort subject".to_string()]).collect(),
);
input = "so".to_string();
assert_eq!(
&match_input!(input),
&IntoIterator::into_iter(["sort".to_string()]).collect(),
);
input = "so ".to_string();
assert_eq!(&match_input!(input), &HashSet::default(),);
input = "to".to_string();
assert_eq!(
&match_input!(input),
&IntoIterator::into_iter(["toggle".to_string()]).collect(),
);
input = "toggle ".to_string();
assert_eq!(
&match_input!(input),
&IntoIterator::into_iter([
"toggle mouse".to_string(),
"toggle sign".to_string(),
"toggle encrypt".to_string(),
"toggle thread_snooze".to_string()
])
.collect(),
);
}
#[test]
#[ignore]
fn test_parser_interactive() {
use std::io;
let mut input = String::new();
loop {
input.clear();
print!("> ");
match io::stdin().read_line(&mut input) {
Ok(_n) => {
println!("Input is {:?}", input.as_str().trim());
let mut sugg = Default::default();
let mut vec = vec![];
//print!("{}", input);
for (_tags, _desc, tokens) in COMMAND_COMPLETION.iter() {
//println!("{:?}, {:?}, {:?}", _tags, _desc, tokens);
let m = tokens.matches(&mut input.as_str().trim(), &mut sugg);
if !m.is_empty() {
vec.push(tokens);
//print!("{:?} ", desc);
//println!(" result = {:#?}\n\n", m);
}
}
println!(
"suggestions = {:#?}",
sugg.into_iter()
.zip(vec.into_iter())
.map(|(s, v)| format!(
"{}{} {:?}",
input.as_str().trim(),
if input.trim().is_empty() {
s.trim()
} else {
s.as_str()
},
v
))
.collect::<Vec<String>>()
);
if input.trim() == "quit" {
break;
}
}
Err(error) => println!("error: {}", error),
}
}
println!("alright");
}
/// Get command suggestions for input
pub fn command_completion_suggestions(input: &str) -> Vec<String> {
use crate::melib::ShellExpandTrait;
@ -1124,3 +1023,110 @@ pub fn command_completion_suggestions(input: &str) -> Vec<String> {
.map(|s| format!("{}{}", input, s.as_str()))
.collect::<Vec<String>>()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_command_parser() {
let mut input = "sort".to_string();
macro_rules! match_input {
($input:expr) => {{
let mut sugg = Default::default();
let mut vec = vec![];
//print!("{}", $input);
for (_tags, _desc, tokens) in COMMAND_COMPLETION.iter() {
//println!("{:?}, {:?}, {:?}", _tags, _desc, tokens);
let m = tokens.matches(&mut $input.as_str(), &mut sugg);
if !m.is_empty() {
vec.push(tokens);
//print!("{:?} ", desc);
//println!(" result = {:#?}\n\n", m);
}
}
//println!("suggestions = {:#?}", sugg);
sugg.into_iter()
.map(|s| format!("{}{}", $input.as_str(), s.as_str()))
.collect::<HashSet<String>>()
}};
}
assert_eq!(
&match_input!(input),
&IntoIterator::into_iter(["sort date".to_string(), "sort subject".to_string()])
.collect(),
);
input = "so".to_string();
assert_eq!(
&match_input!(input),
&IntoIterator::into_iter(["sort".to_string()]).collect(),
);
input = "so ".to_string();
assert_eq!(&match_input!(input), &HashSet::default(),);
input = "to".to_string();
assert_eq!(
&match_input!(input),
&IntoIterator::into_iter(["toggle".to_string()]).collect(),
);
input = "toggle ".to_string();
assert_eq!(
&match_input!(input),
&IntoIterator::into_iter([
"toggle mouse".to_string(),
"toggle sign".to_string(),
"toggle encrypt".to_string(),
"toggle thread_snooze".to_string()
])
.collect(),
);
}
#[test]
#[ignore]
fn test_parser_interactive() {
use std::io;
let mut input = String::new();
loop {
input.clear();
print!("> ");
match io::stdin().read_line(&mut input) {
Ok(_n) => {
println!("Input is {:?}", input.as_str().trim());
let mut sugg = Default::default();
let mut vec = vec![];
//print!("{}", input);
for (_tags, _desc, tokens) in COMMAND_COMPLETION.iter() {
//println!("{:?}, {:?}, {:?}", _tags, _desc, tokens);
let m = tokens.matches(&mut input.as_str().trim(), &mut sugg);
if !m.is_empty() {
vec.push(tokens);
//print!("{:?} ", desc);
//println!(" result = {:#?}\n\n", m);
}
}
println!(
"suggestions = {:#?}",
sugg.into_iter()
.zip(vec.into_iter())
.map(|(s, v)| format!(
"{}{} {:?}",
input.as_str().trim(),
if input.trim().is_empty() {
s.trim()
} else {
s.as_str()
},
v
))
.collect::<Vec<String>>()
);
if input.trim() == "quit" {
break;
}
}
Err(error) => println!("error: {}", error),
}
}
println!("alright");
}
}

View File

@ -2448,7 +2448,7 @@ fn attribution_string(
.map(|addr| addr.get_email())
.unwrap_or_else(|| "\"\"".to_string()),
);
melib::datetime::timestamp_to_string(date, Some(fmt.as_str()), posix)
melib::utils::datetime::timestamp_to_string(date, Some(fmt.as_str()), posix)
}
#[cfg(test)]

View File

@ -22,6 +22,8 @@
//! Pre-submission hooks for draft validation and/or transformations.
pub use std::borrow::Cow;
use melib::email::headers::HeaderName;
use super::*;
pub enum HookFn {
@ -153,10 +155,10 @@ impl std::ops::DerefMut for Hook {
}
fn past_date_warn(_ctx: &mut Context, draft: &mut Draft) -> Result<()> {
use melib::datetime::*;
use melib::utils::datetime::*;
if let Some(v) = draft
.headers
.get("Date")
.get(HeaderName::DATE)
.map(rfc822_to_timestamp)
.and_then(Result::ok)
{
@ -181,8 +183,8 @@ pub const PASTDATEWARN: Hook = Hook {
};
fn important_header_warn(_ctx: &mut Context, draft: &mut Draft) -> Result<()> {
for hdr in ["From", "To"] {
match draft.headers.get(hdr).map(melib::Address::list_try_from) {
for hdr in [HeaderName::FROM, HeaderName::TO] {
match draft.headers.get(&hdr).map(melib::Address::list_try_from) {
Some(Ok(_)) => {}
Some(Err(err)) => return Err(format!("{hdr} header value is invalid ({err}).").into()),
None => return Err(format!("{hdr} header is missing and should be present.").into()),
@ -192,8 +194,8 @@ fn important_header_warn(_ctx: &mut Context, draft: &mut Draft) -> Result<()> {
{
match draft
.headers
.get("Date")
.map(melib::datetime::rfc822_to_timestamp)
.get(HeaderName::DATE)
.map(melib::utils::datetime::rfc822_to_timestamp)
{
Some(Err(err)) => return Err(format!("Date header value is invalid ({err}).").into()),
Some(Ok(0)) => return Err("Date header value is invalid.".into()),
@ -201,10 +203,10 @@ fn important_header_warn(_ctx: &mut Context, draft: &mut Draft) -> Result<()> {
}
}
for hdr in ["Cc", "Bcc"] {
for hdr in [HeaderName::CC, HeaderName::BCC] {
if let Some(Err(err)) = draft
.headers
.get(hdr)
.get(&hdr)
.filter(|v| !v.trim().is_empty())
.map(melib::Address::list_try_from)
{
@ -223,7 +225,7 @@ pub const HEADERWARN: Hook = Hook {
fn missing_attachment_warn(_ctx: &mut Context, draft: &mut Draft) -> Result<()> {
if draft
.headers
.get("Subject")
.get(HeaderName::SUBJECT)
.map(|s| s.to_lowercase().contains("attach"))
.unwrap_or(false)
&& draft.attachments.is_empty()
@ -247,7 +249,7 @@ pub const MISSINGATTACHMENTWARN: Hook = Hook {
fn empty_draft_warn(_ctx: &mut Context, draft: &mut Draft) -> Result<()> {
if draft
.headers
.get("Subject")
.get(HeaderName::SUBJECT)
.filter(|v| !v.trim().is_empty())
.is_none()
&& draft.body.trim().is_empty()

View File

@ -764,7 +764,7 @@ impl ConversationsListing {
n / (24 * 60 * 60),
if n / (24 * 60 * 60) == 1 { "" } else { "s" }
),
_ => melib::datetime::timestamp_to_string(
_ => melib::utils::datetime::timestamp_to_string(
epoch,
context
.settings

View File

@ -992,7 +992,7 @@ impl PlainListing {
n if n < 4 * 24 * 60 * 60 => {
format!("{} days ago{}", n / (24 * 60 * 60), " ".repeat(9))
}
_ => melib::datetime::timestamp_to_string(envelope.datetime(), None, false),
_ => melib::utils::datetime::timestamp_to_string(envelope.datetime(), None, false),
}
}

View File

@ -28,8 +28,8 @@ use std::{
};
use melib::{
datetime, email::attachment_types::ContentType, list_management, mailto::Mailto,
parser::BytesExt, Card, Draft, HeaderName, SpecialUsageMailbox,
email::attachment_types::ContentType, list_management, mailto::Mailto, parser::BytesExt,
utils::datetime, Card, Draft, HeaderName, SpecialUsageMailbox,
};
use smallvec::SmallVec;

View File

@ -401,8 +401,8 @@ impl Component for SVGScreenshotFilter {
}
res.push(b);
}
let mut filename = melib::datetime::timestamp_to_string(
melib::datetime::now(),
let mut filename = melib::utils::datetime::timestamp_to_string(
melib::utils::datetime::now(),
Some("meli Screenshot - %e %h %Y %H:%M:%S.svg"),
true,
);

View File

@ -906,7 +906,7 @@ mod pp {
use melib::{
error::{Error, Result},
parsec::*,
utils::parsec::*,
ShellExpandTrait,
};

View File

@ -77,6 +77,15 @@ macro_rules! try_recv_timeout {
}};
}
macro_rules! is_variant {
($n:ident, $($var:tt)+) => {
#[inline]
pub fn $n(&self) -> bool {
matches!(self, Self::$($var)*)
}
};
}
#[derive(Debug, Clone, Default)]
pub enum MailboxStatus {
Available,
@ -88,13 +97,8 @@ pub enum MailboxStatus {
}
impl MailboxStatus {
pub fn is_available(&self) -> bool {
matches!(self, MailboxStatus::Available)
}
pub fn is_parsing(&self) -> bool {
matches!(self, MailboxStatus::Parsing(_, _))
}
is_variant! { is_available, Available }
is_variant! { is_parsing, Parsing(_, _) }
}
#[derive(Debug, Clone)]

View File

@ -34,8 +34,8 @@ use melib::{
escape_double_quote,
Query::{self, *},
},
sqlite3::{self as melib_sqlite3, rusqlite::params, DatabaseDescription},
thread::{SortField, SortOrder},
utils::sqlite3::{self as melib_sqlite3, rusqlite::params, DatabaseDescription},
Error, Result,
};
use smallvec::SmallVec;
@ -497,7 +497,7 @@ mod tests {
#[test]
fn test_query_to_sql() {
use melib::{parsec::Parser, search::query};
use melib::{search::query, utils::parsec::Parser};
assert_eq!(
"(subject LIKE \"%test%\" ) AND (body_text LIKE \"%i%\" ) ",
&query_to_sql(&query().parse_complete("subject:test and i").unwrap().1)

View File

@ -40,6 +40,7 @@ use crossbeam::channel::{unbounded, Receiver, Sender};
use indexmap::{IndexMap, IndexSet};
use melib::{
backends::{AccountHash, BackendEvent, BackendEventConsumer, Backends, RefreshEvent},
utils::datetime,
UnixTimestamp,
};
use smallvec::SmallVec;
@ -542,7 +543,7 @@ impl State {
let mut areas: smallvec::SmallVec<[Area; 8]> =
self.context.dirty_areas.drain(0..).collect();
if self.display_messages_active {
let now = melib::datetime::now();
let now = datetime::now();
if self
.display_messages_expiration_start
.map(|t| t + 5 < now)
@ -687,9 +688,11 @@ impl State {
}
}
let ((x, mut y), box_displ_area_bottom_right) = box_displ_area;
for line in msg_lines.into_iter().chain(Some(String::new())).chain(Some(
melib::datetime::timestamp_to_string(*timestamp, None, false),
)) {
for line in msg_lines
.into_iter()
.chain(Some(String::new()))
.chain(Some(datetime::timestamp_to_string(*timestamp, None, false)))
{
write_string_to_grid(
&line,
&mut self.screen.overlay_grid,
@ -1016,7 +1019,7 @@ impl State {
pub fn rcv_event(&mut self, mut event: UIEvent) {
if let UIEvent::Input(_) = event {
if self.display_messages_expiration_start.is_none() {
self.display_messages_expiration_start = Some(melib::datetime::now());
self.display_messages_expiration_start = Some(datetime::now());
}
}
@ -1171,7 +1174,7 @@ impl State {
.general
.info_message_previous =>
{
self.display_messages_expiration_start = Some(melib::datetime::now());
self.display_messages_expiration_start = Some(datetime::now());
self.display_messages_active = true;
self.display_messages_initialised = false;
self.display_messages_dirty = true;
@ -1181,7 +1184,7 @@ impl State {
UIEvent::Input(ref key)
if *key == self.context.settings.shortcuts.general.info_message_next =>
{
self.display_messages_expiration_start = Some(melib::datetime::now());
self.display_messages_expiration_start = Some(datetime::now());
self.display_messages_active = true;
self.display_messages_initialised = false;
self.display_messages_dirty = true;
@ -1193,7 +1196,7 @@ impl State {
}
UIEvent::StatusEvent(StatusEvent::DisplayMessage(ref msg)) => {
self.display_messages.push(DisplayMessage {
timestamp: melib::datetime::now(),
timestamp: datetime::now(),
msg: msg.clone(),
});
self.display_messages_active = true;