Compare commits
2 Commits
master
...
jmap-statu
Author | SHA1 | Date |
---|---|---|
Manos Pitsidianakis | a05f36341b | |
Manos Pitsidianakis | 659a34bf21 |
|
@ -21,9 +21,9 @@ path = "src/main.rs"
|
|||
name = "meli"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "managesieve-client"
|
||||
path = "src/managesieve.rs"
|
||||
#[[bin]]
|
||||
#name = "managesieve-meli"
|
||||
#path = "src/managesieve.rs"
|
||||
|
||||
#[[bin]]
|
||||
#name = "async"
|
||||
|
|
|
@ -101,12 +101,12 @@ Custom themes can be included in your configuration files or be saved independen
|
|||
directory as TOML files.
|
||||
To start creating a theme right away, you can begin by editing the default theme keys and values:
|
||||
.sp
|
||||
.Dl meli print-default-theme > ~/.config/meli/themes/new_theme.toml
|
||||
.Dl meli --print-default-theme > ~/.config/meli/themes/new_theme.toml
|
||||
.sp
|
||||
.Pa new_theme.toml
|
||||
will now include all keys and values of the "dark" theme.
|
||||
.sp
|
||||
.Dl meli print-loaded-themes
|
||||
.Dl meli --print-loaded-themes
|
||||
.sp
|
||||
will print all loaded themes with the links resolved.
|
||||
.Sh VALID ATTRIBUTE VALUES
|
||||
|
|
|
@ -303,15 +303,18 @@ On startup, meli should evaluate this command which if successful must only retu
|
|||
.Ss JMAP only
|
||||
JMAP specific options
|
||||
.Bl -tag -width 36n
|
||||
.It Ic server_url Ar String
|
||||
.It Ic server_hostname Ar String
|
||||
example:
|
||||
.Qq http://mail.example.com
|
||||
.Qq http://mail.example.com:8080
|
||||
.Qq https://mail.example.com
|
||||
.Qq mail.example.com
|
||||
.It Ic server_username Ar String
|
||||
Server username
|
||||
.It Ic server_password Ar String
|
||||
Server password
|
||||
.It Ic server_port Ar number
|
||||
.Pq Em optional
|
||||
The port to connect to
|
||||
.\" default value
|
||||
.Pq Em 443
|
||||
.It Ic danger_accept_invalid_certs Ar boolean
|
||||
.Pq Em optional
|
||||
Do not validate TLS certificates.
|
||||
|
|
|
@ -77,16 +77,6 @@
|
|||
### Gmail auto saves sent mail to Sent folder, so don't duplicate the effort:
|
||||
#composing.store_sent_mail = false
|
||||
#
|
||||
##[accounts."jmap account"]
|
||||
##root_mailbox = "INBOX"
|
||||
##format = "jmap"
|
||||
##server_url="http://localhost:8080"
|
||||
##server_username="user@hostname.local"
|
||||
##server_password="changeme"
|
||||
##listing.index_style = "Conversations"
|
||||
##identity = "user@hostname.local"
|
||||
##subscribed_mailboxes = ["*", ]
|
||||
##composing.send_mail = 'server_submission'
|
||||
#
|
||||
#[pager]
|
||||
#filter = "COLUMNS=72 /usr/local/bin/pygmentize -l email"
|
||||
|
|
|
@ -105,6 +105,12 @@ impl Default for Backends {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "jmap_backend")]
|
||||
pub const JMAP_ERROR_MSG: &str = "";
|
||||
|
||||
#[cfg(not(feature = "jmap_backend"))]
|
||||
pub const JMAP_ERROR_MSG: &str = "This library build lacks JMAP support. JMAP requires an HTTP client dependency and thus is turned off by default when compiling.";
|
||||
|
||||
#[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";
|
||||
|
@ -219,6 +225,11 @@ impl Backends {
|
|||
{
|
||||
eprint!("{}", NOTMUCH_ERROR_DETAILS);
|
||||
}
|
||||
} else if key == "jmap" {
|
||||
#[cfg(not(feature = "jmap_backend"))]
|
||||
{
|
||||
eprintln!("{}", JMAP_ERROR_MSG);
|
||||
}
|
||||
}
|
||||
panic!("{} is not a valid mail backend", key);
|
||||
}
|
||||
|
@ -247,6 +258,8 @@ impl Backends {
|
|||
key,
|
||||
if cfg!(feature = "notmuch_backend") && key == "notmuch" {
|
||||
NOTMUCH_ERROR_DETAILS
|
||||
} else if !cfg!(feature = "jmap_backend") && key == "jmap" {
|
||||
JMAP_ERROR_MSG
|
||||
} else {
|
||||
""
|
||||
},
|
||||
|
|
|
@ -21,22 +21,16 @@
|
|||
|
||||
use super::{ImapConnection, ImapProtocol, ImapServerConf, UIDStore};
|
||||
use crate::conf::AccountSettings;
|
||||
use crate::email::parser::IResult;
|
||||
use crate::error::{MeliError, Result};
|
||||
use crate::get_conf_val;
|
||||
use crate::imap::RequiredResponses;
|
||||
use nom::{
|
||||
branch::alt, bytes::complete::tag, combinator::map, multi::separated_list1,
|
||||
sequence::separated_pair,
|
||||
branch::alt, bytes::complete::tag, combinator::map, error::Error as NomError, error::ErrorKind,
|
||||
multi::separated_list1, sequence::separated_pair, IResult,
|
||||
};
|
||||
use std::str::FromStr;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::SystemTime;
|
||||
|
||||
pub struct ManageSieveConnection {
|
||||
pub inner: ImapConnection,
|
||||
}
|
||||
|
||||
pub fn managesieve_capabilities(input: &[u8]) -> Result<Vec<(&[u8], &[u8])>> {
|
||||
let (_, ret) = separated_list1(
|
||||
tag(b"\r\n"),
|
||||
|
@ -48,225 +42,26 @@ pub fn managesieve_capabilities(input: &[u8]) -> Result<Vec<(&[u8], &[u8])>> {
|
|||
Ok(ret)
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
|
||||
pub enum ManageSieveResponse<'a> {
|
||||
Ok {
|
||||
code: Option<&'a [u8]>,
|
||||
message: Option<&'a [u8]>,
|
||||
},
|
||||
NoBye {
|
||||
code: Option<&'a [u8]>,
|
||||
message: Option<&'a [u8]>,
|
||||
},
|
||||
}
|
||||
#[test]
|
||||
fn test_managesieve_capabilities() {
|
||||
assert_eq!(managesieve_capabilities(b"\"IMPLEMENTATION\" \"Dovecot Pigeonhole\"\r\n\"SIEVE\" \"fileinto reject envelope encoded-character vacation subaddress comparator-i;ascii-numeric relational regex imap4flags copy include variables body enotify environment mailbox date index ihave duplicate mime foreverypart extracttext\"\r\n\"NOTIFY\" \"mailto\"\r\n\"SASL\" \"PLAIN\"\r\n\"STARTTLS\"\r\n\"VERSION\" \"1.0\"\r\n").unwrap(), vec![
|
||||
(&b"IMPLEMENTATION"[..],&b"Dovecot Pigeonhole"[..]),
|
||||
(&b"SIEVE"[..],&b"fileinto reject envelope encoded-character vacation subaddress comparator-i;ascii-numeric relational regex imap4flags copy include variables body enotify environment mailbox date index ihave duplicate mime foreverypart extracttext"[..]),
|
||||
(&b"NOTIFY"[..],&b"mailto"[..]),
|
||||
(&b"SASL"[..],&b"PLAIN"[..]),
|
||||
(&b"STARTTLS"[..], &b""[..]),
|
||||
(&b"VERSION"[..],&b"1.0"[..])]
|
||||
|
||||
mod parser {
|
||||
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)
|
||||
}
|
||||
|
||||
// *(sieve-name [SP "ACTIVE"] CRLF)
|
||||
// response-oknobye
|
||||
pub fn listscripts(input: &[u8]) -> IResult<&[u8], Vec<(&[u8], bool)>> {
|
||||
let mut it = iterator(
|
||||
input,
|
||||
alt((
|
||||
terminated(
|
||||
map(terminated(sieve_name, tag_no_case(b" ACTIVE")), |r| {
|
||||
(r, true)
|
||||
}),
|
||||
crlf,
|
||||
),
|
||||
terminated(map(sieve_name, |r| (r, false)), crlf),
|
||||
)),
|
||||
);
|
||||
|
||||
let parsed = (&mut it).collect::<Vec<(&[u8], bool)>>();
|
||||
let res: IResult<_, _> = it.finish();
|
||||
let (rest, _) = res?;
|
||||
Ok((rest, parsed))
|
||||
}
|
||||
|
||||
// response-getscript = (sieve-script CRLF response-ok) /
|
||||
// response-nobye
|
||||
pub fn getscript(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
sieve_name(input)
|
||||
}
|
||||
|
||||
pub fn response_oknobye(input: &[u8]) -> IResult<&[u8], ManageSieveResponse> {
|
||||
alt((
|
||||
map(
|
||||
terminated(
|
||||
pair(
|
||||
preceded(
|
||||
tag_no_case(b"ok"),
|
||||
opt(preceded(
|
||||
tag(b" "),
|
||||
delimited(tag(b"("), is_not(")"), tag(b")")),
|
||||
)),
|
||||
),
|
||||
opt(preceded(tag(b" "), sieve_name)),
|
||||
),
|
||||
crlf,
|
||||
),
|
||||
|(code, message)| ManageSieveResponse::Ok { code, message },
|
||||
),
|
||||
map(
|
||||
terminated(
|
||||
pair(
|
||||
preceded(
|
||||
alt((tag_no_case(b"no"), tag_no_case(b"bye"))),
|
||||
opt(preceded(
|
||||
tag(b" "),
|
||||
delimited(tag(b"("), is_not(")"), tag(b")")),
|
||||
)),
|
||||
),
|
||||
opt(preceded(tag(b" "), sieve_name)),
|
||||
),
|
||||
crlf,
|
||||
),
|
||||
|(code, message)| ManageSieveResponse::NoBye { code, message },
|
||||
),
|
||||
))(input)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_managesieve_listscripts() {
|
||||
let input_1 = b"\"summer_script\"\r\n\"vacation_script\"\r\n{13}\r\nclever\"script\r\n\"main_script\" ACTIVE\r\nOK";
|
||||
assert_eq!(
|
||||
terminated(listscripts, tag_no_case(b"OK"))(input_1),
|
||||
Ok((
|
||||
&b""[..],
|
||||
vec![
|
||||
(&b"summer_script"[..], false),
|
||||
(&b"vacation_script"[..], false),
|
||||
(&b"clever\"script"[..], false),
|
||||
(&b"main_script"[..], true)
|
||||
]
|
||||
))
|
||||
);
|
||||
|
||||
let input_2 = b"\"summer_script\"\r\n\"main_script\" active\r\nok";
|
||||
assert_eq!(
|
||||
terminated(listscripts, tag_no_case(b"OK"))(input_2),
|
||||
Ok((
|
||||
&b""[..],
|
||||
vec![(&b"summer_script"[..], false), (&b"main_script"[..], true)]
|
||||
))
|
||||
);
|
||||
let input_3 = b"ok";
|
||||
assert_eq!(
|
||||
terminated(listscripts, tag_no_case(b"OK"))(input_3),
|
||||
Ok((&b""[..], vec![]))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_managesieve_general() {
|
||||
assert_eq!(managesieve_capabilities(b"\"IMPLEMENTATION\" \"Dovecot Pigeonhole\"\r\n\"SIEVE\" \"fileinto reject envelope encoded-character vacation subaddress comparator-i;ascii-numeric relational regex imap4flags copy include variables body enotify environment mailbox date index ihave duplicate mime foreverypart extracttext\"\r\n\"NOTIFY\" \"mailto\"\r\n\"SASL\" \"PLAIN\"\r\n\"STARTTLS\"\r\n\"VERSION\" \"1.0\"\r\n").unwrap(), vec![
|
||||
(&b"IMPLEMENTATION"[..],&b"Dovecot Pigeonhole"[..]),
|
||||
(&b"SIEVE"[..],&b"fileinto reject envelope encoded-character vacation subaddress comparator-i;ascii-numeric relational regex imap4flags copy include variables body enotify environment mailbox date index ihave duplicate mime foreverypart extracttext"[..]),
|
||||
(&b"NOTIFY"[..],&b"mailto"[..]),
|
||||
(&b"SASL"[..],&b"PLAIN"[..]),
|
||||
(&b"STARTTLS"[..], &b""[..]),
|
||||
(&b"VERSION"[..],&b"1.0"[..])]
|
||||
|
||||
);
|
||||
|
||||
let response_ok = b"OK (WARNINGS) \"line 8: server redirect action limit is 2, this redirect might be ignored\"\r\n";
|
||||
assert_eq!(
|
||||
response_oknobye(response_ok),
|
||||
Ok((
|
||||
&b""[..],
|
||||
ManageSieveResponse::Ok {
|
||||
code: Some(&b"WARNINGS"[..]),
|
||||
message: Some(&b"line 8: server redirect action limit is 2, this redirect might be ignored"[..]),
|
||||
}
|
||||
))
|
||||
);
|
||||
let response_ok = b"OK (WARNINGS)\r\n";
|
||||
assert_eq!(
|
||||
response_oknobye(response_ok),
|
||||
Ok((
|
||||
&b""[..],
|
||||
ManageSieveResponse::Ok {
|
||||
code: Some(&b"WARNINGS"[..]),
|
||||
message: None,
|
||||
}
|
||||
))
|
||||
);
|
||||
let response_ok =
|
||||
b"OK \"line 8: server redirect action limit is 2, this redirect might be ignored\"\r\n";
|
||||
assert_eq!(
|
||||
response_oknobye(response_ok),
|
||||
Ok((
|
||||
&b""[..],
|
||||
ManageSieveResponse::Ok {
|
||||
code: None,
|
||||
message: Some(&b"line 8: server redirect action limit is 2, this redirect might be ignored"[..]),
|
||||
}
|
||||
))
|
||||
);
|
||||
let response_ok = b"Ok\r\n";
|
||||
assert_eq!(
|
||||
response_oknobye(response_ok),
|
||||
Ok((
|
||||
&b""[..],
|
||||
ManageSieveResponse::Ok {
|
||||
code: None,
|
||||
message: None,
|
||||
}
|
||||
))
|
||||
);
|
||||
|
||||
let response_nobye = b"No (NONEXISTENT) \"There is no script by that name\"\r\n";
|
||||
assert_eq!(
|
||||
response_oknobye(response_nobye),
|
||||
Ok((
|
||||
&b""[..],
|
||||
ManageSieveResponse::NoBye {
|
||||
code: Some(&b"NONEXISTENT"[..]),
|
||||
message: Some(&b"There is no script by that name"[..]),
|
||||
}
|
||||
))
|
||||
);
|
||||
let response_nobye = b"No (NONEXISTENT) {31}\r\nThere is no script by that name\r\n";
|
||||
assert_eq!(
|
||||
response_oknobye(response_nobye),
|
||||
Ok((
|
||||
&b""[..],
|
||||
ManageSieveResponse::NoBye {
|
||||
code: Some(&b"NONEXISTENT"[..]),
|
||||
message: Some(&b"There is no script by that name"[..]),
|
||||
}
|
||||
))
|
||||
);
|
||||
|
||||
let response_nobye = b"No\r\n";
|
||||
assert_eq!(
|
||||
response_oknobye(response_nobye),
|
||||
Ok((
|
||||
&b""[..],
|
||||
ManageSieveResponse::NoBye {
|
||||
code: None,
|
||||
message: None,
|
||||
}
|
||||
))
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Return a byte sequence surrounded by "s and decoded if necessary
|
||||
pub fn quoted_raw(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
if input.is_empty() || input[0] != b'"' {
|
||||
return Err(nom::Err::Error((input, "empty").into()));
|
||||
return Err(nom::Err::Error(NomError {
|
||||
input,
|
||||
code: ErrorKind::Tag,
|
||||
}));
|
||||
}
|
||||
|
||||
let mut i = 1;
|
||||
|
@ -277,199 +72,91 @@ pub fn quoted_raw(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
|||
i += 1;
|
||||
}
|
||||
|
||||
Err(nom::Err::Error((input, "no quotes").into()))
|
||||
Err(nom::Err::Error(NomError {
|
||||
input,
|
||||
code: ErrorKind::Tag,
|
||||
}))
|
||||
}
|
||||
|
||||
impl ManageSieveConnection {
|
||||
pub fn new(
|
||||
account_hash: crate::backends::AccountHash,
|
||||
account_name: String,
|
||||
s: &AccountSettings,
|
||||
event_consumer: crate::backends::BackendEventConsumer,
|
||||
) -> Result<Self> {
|
||||
let server_hostname = get_conf_val!(s["server_hostname"])?;
|
||||
let server_username = get_conf_val!(s["server_username"])?;
|
||||
let server_password = get_conf_val!(s["server_password"])?;
|
||||
let server_port = get_conf_val!(s["server_port"], 4190)?;
|
||||
let danger_accept_invalid_certs: bool =
|
||||
get_conf_val!(s["danger_accept_invalid_certs"], false)?;
|
||||
let timeout = get_conf_val!(s["timeout"], 16_u64)?;
|
||||
let timeout = if timeout == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(std::time::Duration::from_secs(timeout))
|
||||
};
|
||||
let server_conf = ImapServerConf {
|
||||
server_hostname: server_hostname.to_string(),
|
||||
server_username: server_username.to_string(),
|
||||
server_password: server_password.to_string(),
|
||||
server_port,
|
||||
use_starttls: true,
|
||||
use_tls: true,
|
||||
danger_accept_invalid_certs,
|
||||
protocol: ImapProtocol::ManageSieve,
|
||||
timeout,
|
||||
};
|
||||
let uid_store = Arc::new(UIDStore {
|
||||
is_online: Arc::new(Mutex::new((
|
||||
SystemTime::now(),
|
||||
Err(MeliError::new("Account is uninitialised.")),
|
||||
))),
|
||||
..UIDStore::new(
|
||||
account_hash,
|
||||
Arc::new(account_name),
|
||||
event_consumer,
|
||||
server_conf.timeout,
|
||||
)
|
||||
});
|
||||
Ok(Self {
|
||||
inner: ImapConnection::new_connection(&server_conf, uid_store),
|
||||
})
|
||||
}
|
||||
pub trait ManageSieve {
|
||||
fn havespace(&mut self) -> Result<()>;
|
||||
fn putscript(&mut self) -> Result<()>;
|
||||
|
||||
pub async fn havespace(&mut self) -> Result<()> {
|
||||
fn listscripts(&mut self) -> Result<()>;
|
||||
fn setactive(&mut self) -> Result<()>;
|
||||
|
||||
fn getscript(&mut self) -> Result<()>;
|
||||
|
||||
fn deletescript(&mut self) -> Result<()>;
|
||||
fn renamescript(&mut self) -> Result<()>;
|
||||
}
|
||||
|
||||
pub fn new_managesieve_connection(
|
||||
account_hash: crate::backends::AccountHash,
|
||||
account_name: String,
|
||||
s: &AccountSettings,
|
||||
event_consumer: crate::backends::BackendEventConsumer,
|
||||
) -> Result<ImapConnection> {
|
||||
let server_hostname = get_conf_val!(s["server_hostname"])?;
|
||||
let server_username = get_conf_val!(s["server_username"])?;
|
||||
let server_password = get_conf_val!(s["server_password"])?;
|
||||
let server_port = get_conf_val!(s["server_port"], 4190)?;
|
||||
let danger_accept_invalid_certs: bool = get_conf_val!(s["danger_accept_invalid_certs"], false)?;
|
||||
let timeout = get_conf_val!(s["timeout"], 16_u64)?;
|
||||
let timeout = if timeout == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(std::time::Duration::from_secs(timeout))
|
||||
};
|
||||
let server_conf = ImapServerConf {
|
||||
server_hostname: server_hostname.to_string(),
|
||||
server_username: server_username.to_string(),
|
||||
server_password: server_password.to_string(),
|
||||
server_port,
|
||||
use_starttls: true,
|
||||
use_tls: true,
|
||||
danger_accept_invalid_certs,
|
||||
protocol: ImapProtocol::ManageSieve,
|
||||
timeout,
|
||||
};
|
||||
let uid_store = Arc::new(UIDStore {
|
||||
is_online: Arc::new(Mutex::new((
|
||||
SystemTime::now(),
|
||||
Err(MeliError::new("Account is uninitialised.")),
|
||||
))),
|
||||
..UIDStore::new(
|
||||
account_hash,
|
||||
Arc::new(account_name),
|
||||
event_consumer,
|
||||
server_conf.timeout,
|
||||
)
|
||||
});
|
||||
Ok(ImapConnection::new_connection(&server_conf, uid_store))
|
||||
}
|
||||
|
||||
impl ManageSieve for ImapConnection {
|
||||
fn havespace(&mut self) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
fn putscript(&mut self) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn putscript(&mut self, script_name: &[u8], script: &[u8]) -> Result<()> {
|
||||
let mut ret = Vec::new();
|
||||
self.inner
|
||||
.send_literal(format!("Putscript {{{len}+}}\r\n", len = script_name.len()).as_bytes())
|
||||
.await?;
|
||||
self.inner.send_literal(script_name).await?;
|
||||
self.inner
|
||||
.send_literal(format!(" {{{len}+}}\r\n", len = script.len()).as_bytes())
|
||||
.await?;
|
||||
self.inner.send_literal(script).await?;
|
||||
self.inner
|
||||
.read_response(&mut ret, RequiredResponses::empty())
|
||||
.await?;
|
||||
let (_rest, response) = parser::response_oknobye(&ret)?;
|
||||
match response {
|
||||
ManageSieveResponse::Ok { .. } => Ok(()),
|
||||
ManageSieveResponse::NoBye { code, message } => Err(format!(
|
||||
"Could not upload script: {} {}",
|
||||
code.map(|b| String::from_utf8_lossy(b)).unwrap_or_default(),
|
||||
message
|
||||
.map(|b| String::from_utf8_lossy(b))
|
||||
.unwrap_or_default()
|
||||
)
|
||||
.into()),
|
||||
}
|
||||
fn listscripts(&mut self) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
fn setactive(&mut self) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn listscripts(&mut self) -> Result<Vec<(Vec<u8>, bool)>> {
|
||||
let mut ret = Vec::new();
|
||||
self.inner.send_command(b"Listscripts").await?;
|
||||
self.inner
|
||||
.read_response(&mut ret, RequiredResponses::empty())
|
||||
.await?;
|
||||
let (_rest, scripts) =
|
||||
parser::terminated(parser::listscripts, parser::tag_no_case(b"OK"))(&ret)?;
|
||||
Ok(scripts
|
||||
.into_iter()
|
||||
.map(|(n, a)| (n.to_vec(), a))
|
||||
.collect::<Vec<(Vec<u8>, bool)>>())
|
||||
fn getscript(&mut self) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn checkscript(&mut self, script: &[u8]) -> Result<()> {
|
||||
let mut ret = Vec::new();
|
||||
self.inner
|
||||
.send_literal(format!("Checkscript {{{len}+}}\r\n", len = script.len()).as_bytes())
|
||||
.await?;
|
||||
self.inner.send_literal(script).await?;
|
||||
self.inner
|
||||
.read_response(&mut ret, RequiredResponses::empty())
|
||||
.await?;
|
||||
let (_rest, response) = parser::response_oknobye(&ret)?;
|
||||
match response {
|
||||
ManageSieveResponse::Ok { .. } => Ok(()),
|
||||
ManageSieveResponse::NoBye { code, message } => Err(format!(
|
||||
"Checkscript reply: {} {}",
|
||||
code.map(|b| String::from_utf8_lossy(b)).unwrap_or_default(),
|
||||
message
|
||||
.map(|b| String::from_utf8_lossy(b))
|
||||
.unwrap_or_default()
|
||||
)
|
||||
.into()),
|
||||
}
|
||||
fn deletescript(&mut self) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn setactive(&mut self, script_name: &[u8]) -> Result<()> {
|
||||
let mut ret = Vec::new();
|
||||
self.inner
|
||||
.send_literal(format!("Setactive {{{len}+}}\r\n", len = script_name.len()).as_bytes())
|
||||
.await?;
|
||||
self.inner.send_literal(script_name).await?;
|
||||
self.inner
|
||||
.read_response(&mut ret, RequiredResponses::empty())
|
||||
.await?;
|
||||
let (_rest, response) = parser::response_oknobye(&ret)?;
|
||||
match response {
|
||||
ManageSieveResponse::Ok { .. } => Ok(()),
|
||||
ManageSieveResponse::NoBye { code, message } => Err(format!(
|
||||
"Could not set active script: {} {}",
|
||||
code.map(|b| String::from_utf8_lossy(b)).unwrap_or_default(),
|
||||
message
|
||||
.map(|b| String::from_utf8_lossy(b))
|
||||
.unwrap_or_default()
|
||||
)
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn getscript(&mut self, script_name: &[u8]) -> Result<Vec<u8>> {
|
||||
let mut ret = Vec::new();
|
||||
self.inner
|
||||
.send_literal(format!("Getscript {{{len}+}}\r\n", len = script_name.len()).as_bytes())
|
||||
.await?;
|
||||
self.inner.send_literal(script_name).await?;
|
||||
self.inner
|
||||
.read_response(&mut ret, RequiredResponses::empty())
|
||||
.await?;
|
||||
if let Ok((_, ManageSieveResponse::NoBye { code, message })) =
|
||||
parser::response_oknobye(&ret)
|
||||
{
|
||||
return Err(format!(
|
||||
"Could not set active script: {} {}",
|
||||
code.map(|b| String::from_utf8_lossy(b)).unwrap_or_default(),
|
||||
message
|
||||
.map(|b| String::from_utf8_lossy(b))
|
||||
.unwrap_or_default()
|
||||
)
|
||||
.into());
|
||||
}
|
||||
let (_rest, script) =
|
||||
parser::terminated(parser::getscript, parser::tag_no_case(b"OK"))(&ret)?;
|
||||
Ok(script.to_vec())
|
||||
}
|
||||
|
||||
pub async fn deletescript(&mut self, script_name: &[u8]) -> Result<()> {
|
||||
let mut ret = Vec::new();
|
||||
self.inner
|
||||
.send_literal(
|
||||
format!("Deletescript {{{len}+}}\r\n", len = script_name.len()).as_bytes(),
|
||||
)
|
||||
.await?;
|
||||
self.inner.send_literal(script_name).await?;
|
||||
self.inner
|
||||
.read_response(&mut ret, RequiredResponses::empty())
|
||||
.await?;
|
||||
let (_rest, response) = parser::response_oknobye(&ret)?;
|
||||
match response {
|
||||
ManageSieveResponse::Ok { .. } => Ok(()),
|
||||
ManageSieveResponse::NoBye { code, message } => Err(format!(
|
||||
"Could not delete script: {} {}",
|
||||
code.map(|b| String::from_utf8_lossy(b)).unwrap_or_default(),
|
||||
message
|
||||
.map(|b| String::from_utf8_lossy(b))
|
||||
.unwrap_or_default()
|
||||
)
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn renamescript(&mut self) -> Result<()> {
|
||||
fn renamescript(&mut self) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -150,9 +150,8 @@ fn test_imap_required_responses() {
|
|||
assert_eq!(v.len(), 1);
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug)]
|
||||
pub struct Alert(String);
|
||||
|
||||
pub type ImapParseResult<'a, T> = Result<(&'a [u8], T, Option<Alert>)>;
|
||||
pub struct ImapLineIterator<'a> {
|
||||
slice: &'a [u8],
|
||||
|
@ -605,8 +604,8 @@ pub fn fetch_response(input: &[u8]) -> ImapParseResult<FetchResponse<'_>> {
|
|||
i += (input.len() - i - rest.len()) + 1;
|
||||
} else {
|
||||
return debug!(Err(MeliError::new(format!(
|
||||
"Unexpected input while parsing UID FETCH response. Could not parse FLAGS: {:.40}.",
|
||||
String::from_utf8_lossy(&input[i..])
|
||||
"Unexpected input while parsing UID FETCH response. Got: `{:.40}`",
|
||||
String::from_utf8_lossy(input)
|
||||
))));
|
||||
}
|
||||
} else if input[i..].starts_with(b"MODSEQ (") {
|
||||
|
@ -640,8 +639,8 @@ pub fn fetch_response(input: &[u8]) -> ImapParseResult<FetchResponse<'_>> {
|
|||
i += input.len() - i - rest.len();
|
||||
} else {
|
||||
return debug!(Err(MeliError::new(format!(
|
||||
"Unexpected input while parsing UID FETCH response. Could not parse RFC822: {:.40}",
|
||||
String::from_utf8_lossy(&input[i..])
|
||||
"Unexpected input while parsing UID FETCH response. Got: `{:.40}`",
|
||||
String::from_utf8_lossy(input)
|
||||
))));
|
||||
}
|
||||
} else if input[i..].starts_with(b"ENVELOPE (") {
|
||||
|
@ -651,7 +650,7 @@ pub fn fetch_response(input: &[u8]) -> ImapParseResult<FetchResponse<'_>> {
|
|||
i += input.len() - i - rest.len();
|
||||
} else {
|
||||
return debug!(Err(MeliError::new(format!(
|
||||
"Unexpected input while parsing UID FETCH response. Could not parse ENVELOPE: {:.40}",
|
||||
"Unexpected input while parsing UID FETCH response. Got: `{:.40}`",
|
||||
String::from_utf8_lossy(&input[i..])
|
||||
))));
|
||||
}
|
||||
|
@ -673,7 +672,7 @@ pub fn fetch_response(input: &[u8]) -> ImapParseResult<FetchResponse<'_>> {
|
|||
i += input.len() - i - rest.len();
|
||||
} else {
|
||||
return debug!(Err(MeliError::new(format!(
|
||||
"Unexpected input while parsing UID FETCH response. Could not parse BODY[HEADER.FIELDS (REFERENCES)]: {:.40}",
|
||||
"Unexpected input while parsing UID FETCH response. Got: `{:.40}`",
|
||||
String::from_utf8_lossy(&input[i..])
|
||||
))));
|
||||
}
|
||||
|
@ -689,7 +688,7 @@ pub fn fetch_response(input: &[u8]) -> ImapParseResult<FetchResponse<'_>> {
|
|||
i += input.len() - i - rest.len();
|
||||
} else {
|
||||
return debug!(Err(MeliError::new(format!(
|
||||
"Unexpected input while parsing UID FETCH response. Could not parse BODY[HEADER.FIELDS (\"REFERENCES\"): {:.40}",
|
||||
"Unexpected input while parsing UID FETCH response. Got: `{:.40}`",
|
||||
String::from_utf8_lossy(&input[i..])
|
||||
))));
|
||||
}
|
||||
|
@ -737,9 +736,9 @@ pub fn fetch_responses(mut input: &[u8]) -> ImapParseResult<Vec<FetchResponse<'_
|
|||
}
|
||||
Err(err) => {
|
||||
return Err(MeliError::new(format!(
|
||||
"Unexpected input while parsing UID FETCH responses: {} `{:.40}`",
|
||||
err,
|
||||
"Unexpected input while parsing UID FETCH responses: `{:.40}`, {}",
|
||||
String::from_utf8_lossy(input),
|
||||
err
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
@ -935,7 +934,7 @@ pub fn untagged_responses(input: &[u8]) -> ImapParseResult<Option<UntaggedRespon
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_imap_untagged_responses() {
|
||||
fn test_untagged_responses() {
|
||||
use UntaggedResponse::*;
|
||||
assert_eq!(
|
||||
untagged_responses(b"* 2 EXISTS\r\n")
|
||||
|
@ -978,37 +977,6 @@ fn test_imap_untagged_responses() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_imap_fetch_response() {
|
||||
let input: &[u8] = b"* 198 FETCH (UID 7608 FLAGS (\\Seen) ENVELOPE (\"Fri, 24 Jun 2011 10:09:10 +0000\" \"xxxx/xxxx\" ((\"xx@xx.com\" NIL \"xx\" \"xx.com\")) NIL NIL ((\"xx@xx\" NIL \"xx\" \"xx.com\")) ((\"'xx, xx'\" NIL \"xx.xx\" \"xx.com\") (\"xx.xx@xx.com\" NIL \"xx.xx\" \"xx.com\") (\"'xx'\" NIL \"xx.xx\" \"xx.com\") (\"'xx xx'\" NIL \"xx.xx\" \"xx.com\") (\"xx.xx@xx.com\" NIL \"xx.xx\" \"xx.com\")) NIL NIL \"<xx@xx.com>\") BODY[HEADER.FIELDS (REFERENCES)] {2}\r\n\r\nBODYSTRUCTURE ((\"text\" \"html\" (\"charset\" \"us-ascii\") \"<xx@xx>\" NIL \"7BIT\" 17236 232 NIL NIL NIL NIL)(\"image\" \"jpeg\" (\"name\" \"image001.jpg\") \"<image001.jpg@xx.xx>\" \"image001.jpg\" \"base64\" 1918 NIL (\"inline\" (\"filename\" \"image001.jpg\" \"size\" \"1650\" \"creation-date\" \"Sun, 09 Aug 2015 20:56:04 GMT\" \"modification-date\" \"Sun, 14 Aug 2022 22:11:45 GMT\")) NIL NIL) \"related\" (\"boundary\" \"xx--xx\" \"type\" \"text/html\") NIL \"en-US\"))\r\n";
|
||||
|
||||
let mut address = SmallVec::new();
|
||||
address.push(Address::new(None, "xx@xx.com".to_string()));
|
||||
let mut env = Envelope::new(0);
|
||||
env.set_subject("xxxx/xxxx".as_bytes().to_vec());
|
||||
env.set_date("Fri, 24 Jun 2011 10:09:10 +0000".as_bytes());
|
||||
env.set_from(address.clone());
|
||||
env.set_to(address);
|
||||
env.set_message_id("<xx@xx.com>".as_bytes());
|
||||
assert_eq!(
|
||||
fetch_response(input).unwrap(),
|
||||
(
|
||||
&b""[..],
|
||||
FetchResponse {
|
||||
uid: Some(7608),
|
||||
message_sequence_number: 198,
|
||||
flags: Some((Flag::SEEN, vec![])),
|
||||
modseq: None,
|
||||
body: None,
|
||||
references: None,
|
||||
envelope: Some(env),
|
||||
raw_fetch_value: input,
|
||||
},
|
||||
None
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
pub fn search_results<'a>(input: &'a [u8]) -> IResult<&'a [u8], Vec<ImapNum>> {
|
||||
alt((
|
||||
|input: &'a [u8]| -> IResult<&'a [u8], Vec<ImapNum>> {
|
||||
|
@ -1159,7 +1127,7 @@ pub fn select_response(input: &[u8]) -> Result<SelectResponse> {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_imap_select_response() {
|
||||
fn test_select_response() {
|
||||
let r = b"* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)\r\n* OK [PERMANENTFLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft \\*)] Flags permitted.\r\n* 45 EXISTS\r\n* 0 RECENT\r\n* OK [UNSEEN 16] First unseen.\r\n* OK [UIDVALIDITY 1554422056] UIDs valid\r\n* OK [UIDNEXT 50] Predicted next UID\r\n";
|
||||
|
||||
assert_eq!(
|
||||
|
@ -1379,12 +1347,6 @@ pub fn envelope(input: &[u8]) -> IResult<&[u8], Envelope> {
|
|||
))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_imap_envelope() {
|
||||
let input: &[u8] = b"(\"Fri, 24 Jun 2011 10:09:10 +0000\" \"xxxx/xxxx\" ((\"xx@xx.com\" NIL \"xx\" \"xx.com\")) NIL NIL ((\"xx@xx\" NIL \"xx\" \"xx.com\")) ((\"'xx, xx'\" NIL \"xx.xx\" \"xx.com\") (\"xx.xx@xx.com\" NIL \"xx.xx\" \"xx.com\") (\"'xx'\" NIL \"xx.xx\" \"xx.com\") (\"'xx xx'\" NIL \"xx.xx\" \"xx.com\") (\"xx.xx@xx.com\" NIL \"xx.xx\" \"xx.com\")) NIL NIL \"<xx@xx.com>\")";
|
||||
_ = envelope(input).unwrap();
|
||||
}
|
||||
|
||||
/* Helper to build StrBuilder for Address structs */
|
||||
macro_rules! str_builder {
|
||||
($offset:expr, $length:expr) => {
|
||||
|
@ -1404,7 +1366,7 @@ pub fn envelope_addresses<'a>(
|
|||
|input: &'a [u8]| -> IResult<&'a [u8], Option<SmallVec<[Address; 1]>>> {
|
||||
let (input, _) = tag("(")(input)?;
|
||||
let (input, envelopes) = fold_many1(
|
||||
delimited(tag("("), envelope_address, alt((tag(") "), tag(")")))),
|
||||
delimited(tag("("), envelope_address, tag(")")),
|
||||
SmallVec::new,
|
||||
|mut acc, item| {
|
||||
acc.push(item);
|
||||
|
@ -1664,12 +1626,12 @@ pub fn mailbox_token(input: &'_ [u8]) -> IResult<&'_ [u8], std::borrow::Cow<'_,
|
|||
}
|
||||
|
||||
// astring = 1*ASTRING-CHAR / string
|
||||
pub fn astring_token(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
fn astring_token(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
alt((string_token, astring_char))(input)
|
||||
}
|
||||
|
||||
// string = quoted / literal
|
||||
pub fn string_token(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
fn string_token(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
if let Ok((r, o)) = literal(input) {
|
||||
return Ok((r, o));
|
||||
}
|
||||
|
|
|
@ -100,9 +100,10 @@ pub struct EnvelopeCache {
|
|||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct JmapServerConf {
|
||||
pub server_url: String,
|
||||
pub server_hostname: String,
|
||||
pub server_username: String,
|
||||
pub server_password: String,
|
||||
pub server_port: u16,
|
||||
pub danger_accept_invalid_certs: bool,
|
||||
pub timeout: Option<Duration>,
|
||||
}
|
||||
|
@ -138,9 +139,10 @@ macro_rules! get_conf_val {
|
|||
impl JmapServerConf {
|
||||
pub fn new(s: &AccountSettings) -> Result<Self> {
|
||||
Ok(JmapServerConf {
|
||||
server_url: get_conf_val!(s["server_url"])?.to_string(),
|
||||
server_hostname: get_conf_val!(s["server_hostname"])?.to_string(),
|
||||
server_username: get_conf_val!(s["server_username"])?.to_string(),
|
||||
server_password: get_conf_val!(s["server_password"])?.to_string(),
|
||||
server_port: get_conf_val!(s["server_port"], 443)?,
|
||||
danger_accept_invalid_certs: get_conf_val!(s["danger_accept_invalid_certs"], false)?,
|
||||
timeout: get_conf_val!(s["timeout"], 16_u64).map(|t| {
|
||||
if t == 0 {
|
||||
|
@ -453,14 +455,7 @@ impl MailBackend for JmapType {
|
|||
};
|
||||
let res_text = res.text().await?;
|
||||
|
||||
let upload_response: UploadResponse = match serde_json::from_str(&res_text) {
|
||||
Err(err) => {
|
||||
let err = MeliError::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);
|
||||
}
|
||||
Ok(s) => s,
|
||||
};
|
||||
let upload_response: UploadResponse = serde_json::from_str(&res_text)?;
|
||||
let mut req = Request::new(conn.request_no.clone());
|
||||
let creation_id: Id<EmailObject> = "1".to_string().into();
|
||||
let mut email_imports = HashMap::default();
|
||||
|
@ -484,14 +479,7 @@ impl MailBackend for JmapType {
|
|||
.await?;
|
||||
let res_text = res.text().await?;
|
||||
|
||||
let mut v: MethodResponse = match serde_json::from_str(&res_text) {
|
||||
Err(err) => {
|
||||
let err = MeliError::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);
|
||||
}
|
||||
Ok(s) => s,
|
||||
};
|
||||
let mut v: MethodResponse = serde_json::from_str(&res_text)?;
|
||||
let m = ImportResponse::try_from(v.method_responses.remove(0)).or_else(|err| {
|
||||
let ierr: Result<ImportError> =
|
||||
serde_json::from_str(&res_text).map_err(|err| err.into());
|
||||
|
@ -565,14 +553,7 @@ impl MailBackend for JmapType {
|
|||
.await?;
|
||||
|
||||
let res_text = res.text().await?;
|
||||
let mut v: MethodResponse = match serde_json::from_str(&res_text) {
|
||||
Err(err) => {
|
||||
let err = MeliError::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);
|
||||
}
|
||||
Ok(s) => s,
|
||||
};
|
||||
let mut v: MethodResponse = serde_json::from_str(&res_text).unwrap();
|
||||
*store.online_status.lock().await = (std::time::Instant::now(), Ok(()));
|
||||
let m = QueryResponse::<EmailObject>::try_from(v.method_responses.remove(0))?;
|
||||
let QueryResponse::<EmailObject> { ids, .. } = m;
|
||||
|
@ -668,14 +649,7 @@ 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 = MeliError::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);
|
||||
}
|
||||
Ok(s) => s,
|
||||
};
|
||||
let mut v: MethodResponse = serde_json::from_str(&res_text).unwrap();
|
||||
*store.online_status.lock().await = (std::time::Instant::now(), Ok(()));
|
||||
let m = SetResponse::<EmailObject>::try_from(v.method_responses.remove(0))?;
|
||||
if let Some(ids) = m.not_updated {
|
||||
|
@ -780,14 +754,7 @@ impl MailBackend for JmapType {
|
|||
*{"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 = MeliError::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);
|
||||
}
|
||||
Ok(s) => s,
|
||||
};
|
||||
let mut v: MethodResponse = serde_json::from_str(&res_text).unwrap();
|
||||
*store.online_status.lock().await = (std::time::Instant::now(), Ok(()));
|
||||
let m = SetResponse::<EmailObject>::try_from(v.method_responses.remove(0))?;
|
||||
if let Some(ids) = m.not_updated {
|
||||
|
@ -937,9 +904,10 @@ impl JmapType {
|
|||
.unwrap_or_else(|| Ok($default))
|
||||
};
|
||||
}
|
||||
get_conf_val!(s["server_url"])?;
|
||||
get_conf_val!(s["server_hostname"])?;
|
||||
get_conf_val!(s["server_username"])?;
|
||||
get_conf_val!(s["server_password"])?;
|
||||
get_conf_val!(s["server_port"], 443)?;
|
||||
get_conf_val!(s["danger_accept_invalid_certs"], false)?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
*/
|
||||
|
||||
use super::*;
|
||||
use crate::error::IntoMeliError;
|
||||
use isahc::config::Configurable;
|
||||
use std::sync::MutexGuard;
|
||||
|
||||
|
@ -34,6 +35,12 @@ pub struct JmapConnection {
|
|||
|
||||
impl JmapConnection {
|
||||
pub fn new(server_conf: &JmapServerConf, store: Arc<Store>) -> Result<Self> {
|
||||
(store.event_consumer)(
|
||||
store.account_hash,
|
||||
crate::backends::BackendEvent::AccountStateChange {
|
||||
message: "Creating connection.".into(),
|
||||
},
|
||||
);
|
||||
let client = HttpClient::builder()
|
||||
.timeout(std::time::Duration::from_secs(10))
|
||||
.redirect_policy(RedirectPolicy::Limit(10))
|
||||
|
@ -53,25 +60,42 @@ impl JmapConnection {
|
|||
})
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn account_state_change(&self, message: impl Into<std::borrow::Cow<'static, str>>) {
|
||||
let message = message.into();
|
||||
(self.store.event_consumer)(
|
||||
self.store.account_hash,
|
||||
crate::backends::BackendEvent::AccountStateChange { message },
|
||||
);
|
||||
}
|
||||
|
||||
pub async fn connect(&mut self) -> Result<()> {
|
||||
if self.store.online_status.lock().await.1.is_ok() {
|
||||
return Ok(());
|
||||
}
|
||||
let mut jmap_session_resource_url = self.server_conf.server_url.to_string();
|
||||
let mut jmap_session_resource_url = self.server_conf.server_hostname.to_string();
|
||||
if self.server_conf.server_port != 443 {
|
||||
jmap_session_resource_url.push(':');
|
||||
jmap_session_resource_url.push_str(&self.server_conf.server_port.to_string());
|
||||
}
|
||||
jmap_session_resource_url.push_str("/.well-known/jmap");
|
||||
self.account_state_change(format!("Requesting {}…", &jmap_session_resource_url));
|
||||
|
||||
let mut req = self.client.get_async(&jmap_session_resource_url).await.map_err(|err| {
|
||||
let err = MeliError::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
|
||||
})?;
|
||||
let mut req = match self.client.get_async(&jmap_session_resource_url).await {
|
||||
Err(err) => {
|
||||
let err = err.set_err_summary(format!("Could not connect to JMAP server endpoint for {}. Is your server hostname 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_hostname));
|
||||
*self.store.online_status.lock().await = (Instant::now(), Err(err.clone()));
|
||||
return Err(err);
|
||||
}
|
||||
Ok(r) => r,
|
||||
};
|
||||
|
||||
if !req.status().is_success() {
|
||||
let kind: crate::error::NetworkErrorKind = req.status().into();
|
||||
let res_text = req.text().await.unwrap_or_default();
|
||||
let err = MeliError::new(format!(
|
||||
"Could not connect to JMAP server endpoint for {}. Reply from server: {}",
|
||||
&self.server_conf.server_url, res_text
|
||||
&self.server_conf.server_hostname, res_text
|
||||
))
|
||||
.set_kind(kind.into());
|
||||
*self.store.online_status.lock().await = (Instant::now(), Err(err.clone()));
|
||||
|
@ -82,7 +106,7 @@ impl JmapConnection {
|
|||
|
||||
let session: JmapSession = match serde_json::from_str(&res_text) {
|
||||
Err(err) => {
|
||||
let err = MeliError::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 = err.set_err_summary(format!("Could not connect to JMAP server endpoint for {}. Is your server hostname 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_hostname, &res_text));
|
||||
*self.store.online_status.lock().await = (Instant::now(), Err(err.clone()));
|
||||
return Err(err);
|
||||
}
|
||||
|
@ -92,7 +116,7 @@ impl JmapConnection {
|
|||
.capabilities
|
||||
.contains_key("urn:ietf:params:jmap:core")
|
||||
{
|
||||
let err = MeliError::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 = MeliError::new(format!("Server {} did not return JMAP Core capability (urn:ietf:params:jmap:core). Returned capabilities were: {}", &self.server_conf.server_hostname, 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);
|
||||
}
|
||||
|
@ -100,7 +124,7 @@ impl JmapConnection {
|
|||
.capabilities
|
||||
.contains_key("urn:ietf:params:jmap:mail")
|
||||
{
|
||||
let err = MeliError::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 = MeliError::new(format!("Server {} does not support JMAP Mail capability (urn:ietf:params:jmap:mail). Returned capabilities were: {}", &self.server_conf.server_hostname, 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);
|
||||
}
|
||||
|
@ -192,14 +216,7 @@ impl JmapConnection {
|
|||
|
||||
let res_text = res.text().await?;
|
||||
debug!(&res_text);
|
||||
let mut v: MethodResponse = match serde_json::from_str(&res_text) {
|
||||
Err(err) => {
|
||||
let err = MeliError::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);
|
||||
}
|
||||
Ok(s) => s,
|
||||
};
|
||||
let mut v: MethodResponse = serde_json::from_str(&res_text).unwrap();
|
||||
let changes_response =
|
||||
ChangesResponse::<EmailObject>::try_from(v.method_responses.remove(0))?;
|
||||
if changes_response.new_state == current_state {
|
||||
|
|
|
@ -842,8 +842,7 @@ pub struct EmailQueryChangesResponse {
|
|||
impl std::convert::TryFrom<&RawValue> for EmailQueryChangesResponse {
|
||||
type Error = crate::error::MeliError;
|
||||
fn try_from(t: &RawValue) -> Result<EmailQueryChangesResponse> {
|
||||
let res: (String, EmailQueryChangesResponse, String) =
|
||||
serde_json::from_str(t.get()).map_err(|err| crate::error::MeliError::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())?;
|
||||
assert_eq!(&res.0, "Email/queryChanges");
|
||||
Ok(res.1)
|
||||
}
|
||||
|
|
|
@ -184,8 +184,7 @@ pub struct ImportResponse {
|
|||
impl std::convert::TryFrom<&RawValue> for ImportResponse {
|
||||
type Error = crate::error::MeliError;
|
||||
fn try_from(t: &RawValue) -> Result<ImportResponse> {
|
||||
let res: (String, ImportResponse, String) =
|
||||
serde_json::from_str(t.get()).map_err(|err| crate::error::MeliError::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, ImportResponse, String) = serde_json::from_str(t.get())?;
|
||||
assert_eq!(&res.0, &ImportCall::NAME);
|
||||
Ok(res.1)
|
||||
}
|
||||
|
|
|
@ -85,6 +85,7 @@ pub struct JsonResponse<'a> {
|
|||
}
|
||||
|
||||
pub async fn get_mailboxes(conn: &JmapConnection) -> Result<HashMap<MailboxHash, JmapMailbox>> {
|
||||
conn.account_state_change("Fetching mailbox list…");
|
||||
let seq = get_request_no!(conn.request_no);
|
||||
let api_url = conn.session.lock().unwrap().api_url.clone();
|
||||
let mut res = conn
|
||||
|
@ -102,14 +103,7 @@ pub async fn get_mailboxes(conn: &JmapConnection) -> Result<HashMap<MailboxHash,
|
|||
.await?;
|
||||
|
||||
let res_text = res.text().await?;
|
||||
let mut v: MethodResponse = match serde_json::from_str(&res_text) {
|
||||
Err(err) => {
|
||||
let err = MeliError::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);
|
||||
}
|
||||
Ok(s) => s,
|
||||
};
|
||||
let mut v: MethodResponse = serde_json::from_str(&res_text).unwrap();
|
||||
*conn.store.online_status.lock().await = (std::time::Instant::now(), Ok(()));
|
||||
let m = GetResponse::<MailboxObject>::try_from(v.method_responses.remove(0))?;
|
||||
let GetResponse::<MailboxObject> {
|
||||
|
@ -187,6 +181,7 @@ pub async fn get_message_list(
|
|||
conn: &JmapConnection,
|
||||
mailbox: &JmapMailbox,
|
||||
) -> Result<Vec<Id<EmailObject>>> {
|
||||
conn.account_state_change(format!("Fetching email list for {}…", mailbox.name()));
|
||||
let email_call: EmailQuery = EmailQuery::new(
|
||||
Query::new()
|
||||
.account_id(conn.mail_account_id().clone())
|
||||
|
@ -209,14 +204,7 @@ pub async fn get_message_list(
|
|||
.await?;
|
||||
|
||||
let res_text = res.text().await?;
|
||||
let mut v: MethodResponse = match serde_json::from_str(&res_text) {
|
||||
Err(err) => {
|
||||
let err = MeliError::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);
|
||||
}
|
||||
Ok(s) => s,
|
||||
};
|
||||
let mut v: MethodResponse = serde_json::from_str(&res_text).unwrap();
|
||||
*conn.store.online_status.lock().await = (std::time::Instant::now(), Ok(()));
|
||||
let m = QueryResponse::<EmailObject>::try_from(v.method_responses.remove(0))?;
|
||||
let QueryResponse::<EmailObject> { ids, .. } = m;
|
||||
|
@ -289,14 +277,7 @@ pub async fn fetch(
|
|||
|
||||
let res_text = res.text().await?;
|
||||
|
||||
let mut v: MethodResponse = match serde_json::from_str(&res_text) {
|
||||
Err(err) => {
|
||||
let err = MeliError::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);
|
||||
}
|
||||
Ok(s) => s,
|
||||
};
|
||||
let mut v: MethodResponse = serde_json::from_str(&res_text).unwrap();
|
||||
let e = GetResponse::<EmailObject>::try_from(v.method_responses.pop().unwrap())?;
|
||||
let query_response = QueryResponse::<EmailObject>::try_from(v.method_responses.pop().unwrap())?;
|
||||
store
|
||||
|
|
|
@ -413,8 +413,7 @@ pub struct GetResponse<OBJ: Object> {
|
|||
impl<OBJ: Object + DeserializeOwned> std::convert::TryFrom<&RawValue> for GetResponse<OBJ> {
|
||||
type Error = crate::error::MeliError;
|
||||
fn try_from(t: &RawValue) -> Result<GetResponse<OBJ>, crate::error::MeliError> {
|
||||
let res: (String, GetResponse<OBJ>, String) =
|
||||
serde_json::from_str(t.get()).map_err(|err| crate::error::MeliError::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))?;
|
||||
let res: (String, GetResponse<OBJ>, String) = serde_json::from_str(t.get())?;
|
||||
assert_eq!(&res.0, &format!("{}/get", OBJ::NAME));
|
||||
Ok(res.1)
|
||||
}
|
||||
|
@ -518,8 +517,7 @@ pub struct QueryResponse<OBJ: Object> {
|
|||
impl<OBJ: Object + DeserializeOwned> std::convert::TryFrom<&RawValue> for QueryResponse<OBJ> {
|
||||
type Error = crate::error::MeliError;
|
||||
fn try_from(t: &RawValue) -> Result<QueryResponse<OBJ>, crate::error::MeliError> {
|
||||
let res: (String, QueryResponse<OBJ>, String) =
|
||||
serde_json::from_str(t.get()).map_err(|err| crate::error::MeliError::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))?;
|
||||
let res: (String, QueryResponse<OBJ>, String) = serde_json::from_str(t.get())?;
|
||||
assert_eq!(&res.0, &format!("{}/query", OBJ::NAME));
|
||||
Ok(res.1)
|
||||
}
|
||||
|
@ -653,8 +651,7 @@ pub struct ChangesResponse<OBJ: Object> {
|
|||
impl<OBJ: Object + DeserializeOwned> std::convert::TryFrom<&RawValue> for ChangesResponse<OBJ> {
|
||||
type Error = crate::error::MeliError;
|
||||
fn try_from(t: &RawValue) -> Result<ChangesResponse<OBJ>, crate::error::MeliError> {
|
||||
let res: (String, ChangesResponse<OBJ>, String) =
|
||||
serde_json::from_str(t.get()).map_err(|err| crate::error::MeliError::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))?;
|
||||
let res: (String, ChangesResponse<OBJ>, String) = serde_json::from_str(t.get())?;
|
||||
assert_eq!(&res.0, &format!("{}/changes", OBJ::NAME));
|
||||
Ok(res.1)
|
||||
}
|
||||
|
@ -852,8 +849,7 @@ pub struct SetResponse<OBJ: Object> {
|
|||
impl<OBJ: Object + DeserializeOwned> std::convert::TryFrom<&RawValue> for SetResponse<OBJ> {
|
||||
type Error = crate::error::MeliError;
|
||||
fn try_from(t: &RawValue) -> Result<SetResponse<OBJ>, crate::error::MeliError> {
|
||||
let res: (String, SetResponse<OBJ>, String) =
|
||||
serde_json::from_str(t.get()).map_err(|err| crate::error::MeliError::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))?;
|
||||
let res: (String, SetResponse<OBJ>, String) = serde_json::from_str(t.get())?;
|
||||
assert_eq!(&res.0, &format!("{}/set", OBJ::NAME));
|
||||
Ok(res.1)
|
||||
}
|
||||
|
|
|
@ -205,7 +205,7 @@ impl MailBackend for MaildirType {
|
|||
let unseen = mailbox.unseen.clone();
|
||||
let total = mailbox.total.clone();
|
||||
let path: PathBuf = mailbox.fs_path().into();
|
||||
let root_mailbox = self.path.to_path_buf();
|
||||
let root_path = self.path.to_path_buf();
|
||||
let map = self.hash_indexes.clone();
|
||||
let mailbox_index = self.mailbox_index.clone();
|
||||
super::stream::MaildirStream::new(
|
||||
|
@ -214,7 +214,7 @@ impl MailBackend for MaildirType {
|
|||
unseen,
|
||||
total,
|
||||
path,
|
||||
root_mailbox,
|
||||
root_path,
|
||||
map,
|
||||
mailbox_index,
|
||||
)
|
||||
|
@ -231,7 +231,7 @@ impl MailBackend for MaildirType {
|
|||
|
||||
let mailbox: &MaildirMailbox = &self.mailboxes[&mailbox_hash];
|
||||
let path: PathBuf = mailbox.fs_path().into();
|
||||
let root_mailbox = self.path.to_path_buf();
|
||||
let root_path = self.path.to_path_buf();
|
||||
let map = self.hash_indexes.clone();
|
||||
let mailbox_index = self.mailbox_index.clone();
|
||||
|
||||
|
@ -266,7 +266,7 @@ impl MailBackend for MaildirType {
|
|||
.lock()
|
||||
.unwrap()
|
||||
.insert(env.hash(), mailbox_hash);
|
||||
let file_name = file.strip_prefix(&root_mailbox).unwrap().to_path_buf();
|
||||
let file_name = file.strip_prefix(&root_path).unwrap().to_path_buf();
|
||||
if let Ok(cached) = cache_dir.place_cache_file(file_name) {
|
||||
/* place result in cache directory */
|
||||
let f = fs::File::create(cached)?;
|
||||
|
@ -334,12 +334,10 @@ impl MailBackend for MaildirType {
|
|||
hasher.write(self.name.as_bytes());
|
||||
hasher.finish()
|
||||
};
|
||||
let root_mailbox = self.path.to_path_buf();
|
||||
watcher
|
||||
.watch(&root_mailbox, RecursiveMode::Recursive)
|
||||
.unwrap();
|
||||
let root_path = self.path.to_path_buf();
|
||||
watcher.watch(&root_path, RecursiveMode::Recursive).unwrap();
|
||||
let cache_dir = xdg::BaseDirectories::with_profile("meli", &self.name).unwrap();
|
||||
debug!("watching {:?}", root_mailbox);
|
||||
debug!("watching {:?}", root_path);
|
||||
let hash_indexes = self.hash_indexes.clone();
|
||||
let mailbox_index = self.mailbox_index.clone();
|
||||
let root_mailbox_hash: MailboxHash = self
|
||||
|
@ -387,7 +385,7 @@ impl MailBackend for MaildirType {
|
|||
let mailbox_hash = get_path_hash!(pathbuf);
|
||||
let file_name = pathbuf
|
||||
.as_path()
|
||||
.strip_prefix(&root_mailbox)
|
||||
.strip_prefix(&root_path)
|
||||
.unwrap()
|
||||
.to_path_buf();
|
||||
if let Ok(env) = add_path_to_index(
|
||||
|
@ -431,7 +429,7 @@ impl MailBackend for MaildirType {
|
|||
&mut hash_indexes_lock.entry(mailbox_hash).or_default();
|
||||
let file_name = pathbuf
|
||||
.as_path()
|
||||
.strip_prefix(&root_mailbox)
|
||||
.strip_prefix(&root_path)
|
||||
.unwrap()
|
||||
.to_path_buf();
|
||||
/* Linear search in hash_index to find old hash */
|
||||
|
@ -593,7 +591,7 @@ impl MailBackend for MaildirType {
|
|||
);
|
||||
let file_name = dest
|
||||
.as_path()
|
||||
.strip_prefix(&root_mailbox)
|
||||
.strip_prefix(&root_path)
|
||||
.unwrap()
|
||||
.to_path_buf();
|
||||
drop(hash_indexes_lock);
|
||||
|
@ -683,7 +681,7 @@ impl MailBackend for MaildirType {
|
|||
}
|
||||
let file_name = dest
|
||||
.as_path()
|
||||
.strip_prefix(&root_mailbox)
|
||||
.strip_prefix(&root_path)
|
||||
.unwrap()
|
||||
.to_path_buf();
|
||||
debug!("filename = {:?}", file_name);
|
||||
|
@ -732,7 +730,7 @@ impl MailBackend for MaildirType {
|
|||
drop(hash_indexes_lock);
|
||||
let file_name = dest
|
||||
.as_path()
|
||||
.strip_prefix(&root_mailbox)
|
||||
.strip_prefix(&root_path)
|
||||
.unwrap()
|
||||
.to_path_buf();
|
||||
if let Ok(env) = add_path_to_index(
|
||||
|
@ -1162,29 +1160,24 @@ impl MaildirType {
|
|||
}
|
||||
Ok(children)
|
||||
}
|
||||
let root_mailbox = PathBuf::from(settings.root_mailbox()).expand();
|
||||
if !root_mailbox.exists() {
|
||||
let root_path = PathBuf::from(settings.root_mailbox()).expand();
|
||||
if !root_path.exists() {
|
||||
return Err(MeliError::new(format!(
|
||||
"Configuration error ({}): root_mailbox `{}` is not a valid directory.",
|
||||
"Configuration error ({}): root_path `{}` is not a valid directory.",
|
||||
settings.name(),
|
||||
settings.root_mailbox.as_str()
|
||||
)));
|
||||
} else if !root_mailbox.is_dir() {
|
||||
} else if !root_path.is_dir() {
|
||||
return Err(MeliError::new(format!(
|
||||
"Configuration error ({}): root_mailbox `{}` is not a directory.",
|
||||
"Configuration error ({}): root_path `{}` is not a directory.",
|
||||
settings.name(),
|
||||
settings.root_mailbox.as_str()
|
||||
)));
|
||||
}
|
||||
|
||||
if let Ok(f) = MaildirMailbox::new(
|
||||
root_mailbox.to_str().unwrap().to_string(),
|
||||
root_mailbox
|
||||
.file_name()
|
||||
.unwrap_or_default()
|
||||
.to_str()
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
root_path.to_str().unwrap().to_string(),
|
||||
root_path.file_name().unwrap().to_str().unwrap().to_string(),
|
||||
None,
|
||||
Vec::with_capacity(0),
|
||||
false,
|
||||
|
@ -1194,7 +1187,7 @@ impl MaildirType {
|
|||
}
|
||||
|
||||
if mailboxes.is_empty() {
|
||||
let children = recurse_mailboxes(&mut mailboxes, settings, &root_mailbox)?;
|
||||
let children = recurse_mailboxes(&mut mailboxes, settings, &root_path)?;
|
||||
for c in &children {
|
||||
if let Some(f) = mailboxes.get_mut(c) {
|
||||
f.parent = None;
|
||||
|
@ -1202,7 +1195,7 @@ impl MaildirType {
|
|||
}
|
||||
} else {
|
||||
let root_hash = *mailboxes.keys().next().unwrap();
|
||||
let children = recurse_mailboxes(&mut mailboxes, settings, &root_mailbox)?;
|
||||
let children = recurse_mailboxes(&mut mailboxes, settings, &root_path)?;
|
||||
for c in &children {
|
||||
if let Some(f) = mailboxes.get_mut(c) {
|
||||
f.parent = Some(root_hash);
|
||||
|
@ -1236,7 +1229,7 @@ impl MaildirType {
|
|||
mailbox_index: Default::default(),
|
||||
event_consumer,
|
||||
collection: Default::default(),
|
||||
path: root_mailbox,
|
||||
path: root_path,
|
||||
}))
|
||||
}
|
||||
|
||||
|
@ -1310,16 +1303,16 @@ impl MaildirType {
|
|||
}
|
||||
|
||||
pub fn validate_config(s: &mut AccountSettings) -> Result<()> {
|
||||
let root_mailbox = PathBuf::from(s.root_mailbox()).expand();
|
||||
if !root_mailbox.exists() {
|
||||
let root_path = PathBuf::from(s.root_mailbox()).expand();
|
||||
if !root_path.exists() {
|
||||
return Err(MeliError::new(format!(
|
||||
"Configuration error ({}): root_mailbox `{}` is not a valid directory.",
|
||||
"Configuration error ({}): root_path `{}` is not a valid directory.",
|
||||
s.name(),
|
||||
s.root_mailbox.as_str()
|
||||
)));
|
||||
} else if !root_mailbox.is_dir() {
|
||||
} else if !root_path.is_dir() {
|
||||
return Err(MeliError::new(format!(
|
||||
"Configuration error ({}): root_mailbox `{}` is not a directory.",
|
||||
"Configuration error ({}): root_path `{}` is not a directory.",
|
||||
s.name(),
|
||||
s.root_mailbox.as_str()
|
||||
)));
|
||||
|
|
|
@ -46,7 +46,7 @@ impl MaildirStream {
|
|||
unseen: Arc<Mutex<usize>>,
|
||||
total: Arc<Mutex<usize>>,
|
||||
mut path: PathBuf,
|
||||
root_mailbox: PathBuf,
|
||||
root_path: PathBuf,
|
||||
map: HashIndexes,
|
||||
mailbox_index: Arc<Mutex<HashMap<EnvelopeHash, MailboxHash>>>,
|
||||
) -> Result<Pin<Box<dyn Stream<Item = Result<Vec<Envelope>>> + Send + 'static>>> {
|
||||
|
@ -73,7 +73,7 @@ impl MaildirStream {
|
|||
mailbox_hash,
|
||||
unseen.clone(),
|
||||
total.clone(),
|
||||
root_mailbox.clone(),
|
||||
root_path.clone(),
|
||||
map.clone(),
|
||||
mailbox_index.clone(),
|
||||
)) as Pin<Box<dyn Future<Output = _> + Send + 'static>>
|
||||
|
@ -91,7 +91,7 @@ impl MaildirStream {
|
|||
mailbox_hash: MailboxHash,
|
||||
unseen: Arc<Mutex<usize>>,
|
||||
total: Arc<Mutex<usize>>,
|
||||
root_mailbox: PathBuf,
|
||||
root_path: PathBuf,
|
||||
map: HashIndexes,
|
||||
mailbox_index: Arc<Mutex<HashMap<EnvelopeHash, MailboxHash>>>,
|
||||
) -> Result<Vec<Envelope>> {
|
||||
|
@ -102,7 +102,7 @@ impl MaildirStream {
|
|||
/* Check if we have a cache file with this email's
|
||||
* filename */
|
||||
let file_name = PathBuf::from(&file)
|
||||
.strip_prefix(&root_mailbox)
|
||||
.strip_prefix(&root_path)
|
||||
.unwrap()
|
||||
.to_path_buf();
|
||||
if let Some(cached) = cache_dir.find_cache_file(&file_name) {
|
||||
|
|
|
@ -475,7 +475,7 @@ impl<K: std::cmp::Eq + std::hash::Hash, V> Deref for RwRef<'_, K, V> {
|
|||
type Target = V;
|
||||
|
||||
fn deref(&self) -> &V {
|
||||
self.guard.get(&self.hash).expect("Hash was not found")
|
||||
self.guard.get(&self.hash).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -486,7 +486,7 @@ pub struct RwRefMut<'g, K: std::cmp::Eq + std::hash::Hash, V> {
|
|||
|
||||
impl<K: std::cmp::Eq + std::hash::Hash, V> DerefMut for RwRefMut<'_, K, V> {
|
||||
fn deref_mut(&mut self) -> &mut V {
|
||||
self.guard.get_mut(&self.hash).expect("Hash was not found")
|
||||
self.guard.get_mut(&self.hash).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -494,6 +494,6 @@ impl<K: std::cmp::Eq + std::hash::Hash, V> Deref for RwRefMut<'_, K, V> {
|
|||
type Target = V;
|
||||
|
||||
fn deref(&self) -> &V {
|
||||
self.guard.get(&self.hash).expect("Hash was not found")
|
||||
self.guard.get(&self.hash).unwrap()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,7 +41,6 @@ use crate::error::{Result, ResultIntoMeliError};
|
|||
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";
|
||||
|
@ -75,36 +74,14 @@ extern "C" {
|
|||
fn gettimeofday(tv: *mut libc::timeval, tz: *mut libc::timezone) -> i32;
|
||||
}
|
||||
|
||||
#[repr(i32)]
|
||||
#[derive(Copy, Clone)]
|
||||
#[allow(dead_code)]
|
||||
enum LocaleCategoryMask {
|
||||
Time = libc::LC_TIME_MASK,
|
||||
All = libc::LC_ALL_MASK,
|
||||
}
|
||||
|
||||
#[repr(i32)]
|
||||
#[derive(Copy, Clone)]
|
||||
#[allow(dead_code)]
|
||||
enum LocaleCategory {
|
||||
Time = libc::LC_TIME,
|
||||
All = libc::LC_ALL,
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "netbsd"))]
|
||||
#[allow(dead_code)]
|
||||
struct Locale {
|
||||
mask: LocaleCategoryMask,
|
||||
category: LocaleCategory,
|
||||
new_locale: libc::locale_t,
|
||||
old_locale: libc::locale_t,
|
||||
}
|
||||
|
||||
#[cfg(target_os = "netbsd")]
|
||||
#[allow(dead_code)]
|
||||
struct Locale {
|
||||
mask: LocaleCategoryMask,
|
||||
category: LocaleCategory,
|
||||
mask: std::os::raw::c_int,
|
||||
old_locale: *const std::os::raw::c_char,
|
||||
}
|
||||
|
||||
|
@ -117,7 +94,7 @@ impl Drop for Locale {
|
|||
}
|
||||
#[cfg(target_os = "netbsd")]
|
||||
unsafe {
|
||||
let _ = libc::setlocale(self.category as c_int, self.old_locale);
|
||||
let _ = libc::setlocale(self.mask, self.old_locale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -126,12 +103,11 @@ impl Drop for Locale {
|
|||
impl Locale {
|
||||
#[cfg(not(target_os = "netbsd"))]
|
||||
fn new(
|
||||
mask: LocaleCategoryMask,
|
||||
category: LocaleCategory,
|
||||
mask: std::os::raw::c_int,
|
||||
locale: *const std::os::raw::c_char,
|
||||
base: libc::locale_t,
|
||||
) -> Result<Self> {
|
||||
let new_locale = unsafe { libc::newlocale(mask as c_int, locale, base) };
|
||||
let new_locale = unsafe { libc::newlocale(mask, locale, base) };
|
||||
if new_locale.is_null() {
|
||||
return Err(nix::Error::last().into());
|
||||
}
|
||||
|
@ -141,32 +117,25 @@ impl Locale {
|
|||
return Err(nix::Error::last().into());
|
||||
}
|
||||
Ok(Locale {
|
||||
mask,
|
||||
category,
|
||||
new_locale,
|
||||
old_locale,
|
||||
})
|
||||
}
|
||||
#[cfg(target_os = "netbsd")]
|
||||
fn new(
|
||||
mask: LocaleCategoryMask,
|
||||
category: LocaleCategory,
|
||||
mask: std::os::raw::c_int,
|
||||
locale: *const std::os::raw::c_char,
|
||||
_base: libc::locale_t,
|
||||
) -> Result<Self> {
|
||||
let old_locale = unsafe { libc::setlocale(category as c_int, std::ptr::null_mut()) };
|
||||
let old_locale = unsafe { libc::setlocale(mask, std::ptr::null_mut()) };
|
||||
if old_locale.is_null() {
|
||||
return Err(nix::Error::last().into());
|
||||
}
|
||||
let new_locale = unsafe { libc::setlocale(category as c_int, locale) };
|
||||
let new_locale = unsafe { libc::setlocale(mask, locale) };
|
||||
if new_locale.is_null() {
|
||||
return Err(nix::Error::last().into());
|
||||
}
|
||||
Ok(Locale {
|
||||
mask,
|
||||
category,
|
||||
old_locale,
|
||||
})
|
||||
Ok(Locale { mask, old_locale })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -197,8 +166,7 @@ pub fn timestamp_to_string(timestamp: UnixTimestamp, fmt: Option<&str>, posix: b
|
|||
let _with_locale: Option<Result<Locale>> = if posix {
|
||||
Some(
|
||||
Locale::new(
|
||||
LocaleCategoryMask::Time,
|
||||
LocaleCategory::Time,
|
||||
libc::LC_TIME,
|
||||
b"C\0".as_ptr() as *const std::os::raw::c_char,
|
||||
std::ptr::null_mut(),
|
||||
)
|
||||
|
@ -336,8 +304,7 @@ where
|
|||
let fmt = unsafe { CStr::from_bytes_with_nul_unchecked(fmt.as_bytes()) };
|
||||
let ret = {
|
||||
let _with_locale = Locale::new(
|
||||
LocaleCategoryMask::Time,
|
||||
LocaleCategory::Time,
|
||||
libc::LC_TIME,
|
||||
b"C\0".as_ptr() as *const std::os::raw::c_char,
|
||||
std::ptr::null_mut(),
|
||||
)
|
||||
|
@ -398,8 +365,7 @@ where
|
|||
let fmt = unsafe { CStr::from_bytes_with_nul_unchecked(fmt.as_bytes()) };
|
||||
let ret = {
|
||||
let _with_locale = Locale::new(
|
||||
LocaleCategoryMask::Time,
|
||||
LocaleCategory::Time,
|
||||
libc::LC_TIME,
|
||||
b"C\0".as_ptr() as *const std::os::raw::c_char,
|
||||
std::ptr::null_mut(),
|
||||
)
|
||||
|
|
|
@ -24,7 +24,7 @@ use std::fs::File;
|
|||
use std::io::prelude::*;
|
||||
use std::time::SystemTime;
|
||||
|
||||
fn random_u64() -> u64 {
|
||||
pub fn random_u64() -> u64 {
|
||||
let mut f = File::open("/dev/urandom").unwrap();
|
||||
let mut buffer = [0; 8];
|
||||
|
||||
|
|
|
@ -316,7 +316,6 @@ pub struct _gpgme_sig_notation {
|
|||
pub _bitfield_1: __BindgenBitfieldUnit<[u8; 4usize], u32>,
|
||||
}
|
||||
#[test]
|
||||
#[allow(deref_nullptr)]
|
||||
fn bindgen_test_layout__gpgme_sig_notation() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<_gpgme_sig_notation>(),
|
||||
|
@ -458,7 +457,6 @@ pub struct _gpgme_engine_info {
|
|||
pub home_dir: *mut ::std::os::raw::c_char,
|
||||
}
|
||||
#[test]
|
||||
#[allow(deref_nullptr)]
|
||||
fn bindgen_test_layout__gpgme_engine_info() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<_gpgme_engine_info>(),
|
||||
|
@ -546,7 +544,6 @@ pub struct _gpgme_tofu_info {
|
|||
pub description: *mut ::std::os::raw::c_char,
|
||||
}
|
||||
#[test]
|
||||
#[allow(deref_nullptr)]
|
||||
fn bindgen_test_layout__gpgme_tofu_info() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<_gpgme_tofu_info>(),
|
||||
|
@ -714,7 +711,6 @@ pub struct _gpgme_subkey {
|
|||
pub keygrip: *mut ::std::os::raw::c_char,
|
||||
}
|
||||
#[test]
|
||||
#[allow(deref_nullptr)]
|
||||
fn bindgen_test_layout__gpgme_subkey() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<_gpgme_subkey>(),
|
||||
|
@ -1076,7 +1072,6 @@ pub struct _gpgme_key_sig {
|
|||
pub _last_notation: gpgme_sig_notation_t,
|
||||
}
|
||||
#[test]
|
||||
#[allow(deref_nullptr)]
|
||||
fn bindgen_test_layout__gpgme_key_sig() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<_gpgme_key_sig>(),
|
||||
|
@ -1346,7 +1341,6 @@ pub struct _gpgme_user_id {
|
|||
pub last_update: ::std::os::raw::c_ulong,
|
||||
}
|
||||
#[test]
|
||||
#[allow(deref_nullptr)]
|
||||
fn bindgen_test_layout__gpgme_user_id() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<_gpgme_user_id>(),
|
||||
|
@ -1562,7 +1556,6 @@ pub struct _gpgme_key {
|
|||
pub last_update: ::std::os::raw::c_ulong,
|
||||
}
|
||||
#[test]
|
||||
#[allow(deref_nullptr)]
|
||||
fn bindgen_test_layout__gpgme_key() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<_gpgme_key>(),
|
||||
|
@ -1915,7 +1908,6 @@ pub struct _gpgme_invalid_key {
|
|||
pub reason: gpgme_error_t,
|
||||
}
|
||||
#[test]
|
||||
#[allow(deref_nullptr)]
|
||||
fn bindgen_test_layout__gpgme_invalid_key() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<_gpgme_invalid_key>(),
|
||||
|
@ -2111,7 +2103,6 @@ pub struct gpgme_io_event_done_data {
|
|||
pub op_err: gpgme_error_t,
|
||||
}
|
||||
#[test]
|
||||
#[allow(deref_nullptr)]
|
||||
fn bindgen_test_layout_gpgme_io_event_done_data() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<gpgme_io_event_done_data>(),
|
||||
|
@ -2162,7 +2153,6 @@ pub struct gpgme_io_cbs {
|
|||
pub event_priv: *mut ::std::os::raw::c_void,
|
||||
}
|
||||
#[test]
|
||||
#[allow(deref_nullptr)]
|
||||
fn bindgen_test_layout_gpgme_io_cbs() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<gpgme_io_cbs>(),
|
||||
|
@ -2288,7 +2278,6 @@ pub struct gpgme_data_cbs {
|
|||
pub release: gpgme_data_release_cb_t,
|
||||
}
|
||||
#[test]
|
||||
#[allow(deref_nullptr)]
|
||||
fn bindgen_test_layout_gpgme_data_cbs() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<gpgme_data_cbs>(),
|
||||
|
@ -2418,7 +2407,6 @@ pub struct _gpgme_op_encrypt_result {
|
|||
pub invalid_recipients: gpgme_invalid_key_t,
|
||||
}
|
||||
#[test]
|
||||
#[allow(deref_nullptr)]
|
||||
fn bindgen_test_layout__gpgme_op_encrypt_result() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<_gpgme_op_encrypt_result>(),
|
||||
|
@ -2526,7 +2514,6 @@ pub struct _gpgme_recipient {
|
|||
pub status: gpgme_error_t,
|
||||
}
|
||||
#[test]
|
||||
#[allow(deref_nullptr)]
|
||||
fn bindgen_test_layout__gpgme_recipient() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<_gpgme_recipient>(),
|
||||
|
@ -2601,7 +2588,6 @@ pub struct _gpgme_op_decrypt_result {
|
|||
pub symkey_algo: *mut ::std::os::raw::c_char,
|
||||
}
|
||||
#[test]
|
||||
#[allow(deref_nullptr)]
|
||||
fn bindgen_test_layout__gpgme_op_decrypt_result() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<_gpgme_op_decrypt_result>(),
|
||||
|
@ -2806,7 +2792,6 @@ pub struct _gpgme_new_signature {
|
|||
pub sig_class: ::std::os::raw::c_uint,
|
||||
}
|
||||
#[test]
|
||||
#[allow(deref_nullptr)]
|
||||
fn bindgen_test_layout__gpgme_new_signature() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<_gpgme_new_signature>(),
|
||||
|
@ -2921,7 +2906,6 @@ pub struct _gpgme_op_sign_result {
|
|||
pub signatures: gpgme_new_signature_t,
|
||||
}
|
||||
#[test]
|
||||
#[allow(deref_nullptr)]
|
||||
fn bindgen_test_layout__gpgme_op_sign_result() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<_gpgme_op_sign_result>(),
|
||||
|
@ -3004,7 +2988,6 @@ pub struct _gpgme_signature {
|
|||
pub key: gpgme_key_t,
|
||||
}
|
||||
#[test]
|
||||
#[allow(deref_nullptr)]
|
||||
fn bindgen_test_layout__gpgme_signature() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<_gpgme_signature>(),
|
||||
|
@ -3248,7 +3231,6 @@ pub struct _gpgme_op_verify_result {
|
|||
pub __bindgen_padding_0: u32,
|
||||
}
|
||||
#[test]
|
||||
#[allow(deref_nullptr)]
|
||||
fn bindgen_test_layout__gpgme_op_verify_result() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<_gpgme_op_verify_result>(),
|
||||
|
@ -3349,7 +3331,6 @@ pub struct _gpgme_import_status {
|
|||
pub status: ::std::os::raw::c_uint,
|
||||
}
|
||||
#[test]
|
||||
#[allow(deref_nullptr)]
|
||||
fn bindgen_test_layout__gpgme_import_status() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<_gpgme_import_status>(),
|
||||
|
@ -3424,7 +3405,6 @@ pub struct _gpgme_op_import_result {
|
|||
pub skipped_v3_keys: ::std::os::raw::c_int,
|
||||
}
|
||||
#[test]
|
||||
#[allow(deref_nullptr)]
|
||||
fn bindgen_test_layout__gpgme_op_import_result() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<_gpgme_op_import_result>(),
|
||||
|
@ -3683,7 +3663,6 @@ pub struct _gpgme_op_genkey_result {
|
|||
pub seckey: gpgme_data_t,
|
||||
}
|
||||
#[test]
|
||||
#[allow(deref_nullptr)]
|
||||
fn bindgen_test_layout__gpgme_op_genkey_result() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<_gpgme_op_genkey_result>(),
|
||||
|
@ -4045,7 +4024,6 @@ pub struct _gpgme_trust_item {
|
|||
pub name: *mut ::std::os::raw::c_char,
|
||||
}
|
||||
#[test]
|
||||
#[allow(deref_nullptr)]
|
||||
fn bindgen_test_layout__gpgme_trust_item() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<_gpgme_trust_item>(),
|
||||
|
@ -4246,7 +4224,6 @@ pub struct _gpgme_op_vfs_mount_result {
|
|||
pub mount_dir: *mut ::std::os::raw::c_char,
|
||||
}
|
||||
#[test]
|
||||
#[allow(deref_nullptr)]
|
||||
fn bindgen_test_layout__gpgme_op_vfs_mount_result() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<_gpgme_op_vfs_mount_result>(),
|
||||
|
@ -4321,7 +4298,6 @@ pub union gpgme_conf_arg__bindgen_ty_1 {
|
|||
_bindgen_union_align: u64,
|
||||
}
|
||||
#[test]
|
||||
#[allow(deref_nullptr)]
|
||||
fn bindgen_test_layout_gpgme_conf_arg__bindgen_ty_1() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<gpgme_conf_arg__bindgen_ty_1>(),
|
||||
|
@ -4383,7 +4359,6 @@ fn bindgen_test_layout_gpgme_conf_arg__bindgen_ty_1() {
|
|||
);
|
||||
}
|
||||
#[test]
|
||||
#[allow(deref_nullptr)]
|
||||
fn bindgen_test_layout_gpgme_conf_arg() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<gpgme_conf_arg>(),
|
||||
|
@ -4448,7 +4423,6 @@ pub struct gpgme_conf_opt {
|
|||
pub user_data: *mut ::std::os::raw::c_void,
|
||||
}
|
||||
#[test]
|
||||
#[allow(deref_nullptr)]
|
||||
fn bindgen_test_layout_gpgme_conf_opt() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<gpgme_conf_opt>(),
|
||||
|
@ -4637,7 +4611,6 @@ pub struct gpgme_conf_comp {
|
|||
pub options: *mut gpgme_conf_opt,
|
||||
}
|
||||
#[test]
|
||||
#[allow(deref_nullptr)]
|
||||
fn bindgen_test_layout_gpgme_conf_comp() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<gpgme_conf_comp>(),
|
||||
|
@ -4745,7 +4718,6 @@ pub struct _gpgme_op_query_swdb_result {
|
|||
pub reldate: ::std::os::raw::c_ulong,
|
||||
}
|
||||
#[test]
|
||||
#[allow(deref_nullptr)]
|
||||
fn bindgen_test_layout__gpgme_op_query_swdb_result() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<_gpgme_op_query_swdb_result>(),
|
||||
|
@ -5286,7 +5258,6 @@ pub struct _gpgme_op_assuan_result {
|
|||
pub err: gpgme_error_t,
|
||||
}
|
||||
#[test]
|
||||
#[allow(deref_nullptr)]
|
||||
fn bindgen_test_layout__gpgme_op_assuan_result() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<_gpgme_op_assuan_result>(),
|
||||
|
@ -5367,7 +5338,6 @@ pub struct __va_list_tag {
|
|||
pub reg_save_area: *mut ::std::os::raw::c_void,
|
||||
}
|
||||
#[test]
|
||||
#[allow(deref_nullptr)]
|
||||
fn bindgen_test_layout___va_list_tag() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<__va_list_tag>(),
|
||||
|
|
|
@ -93,22 +93,6 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
pub fn map_res<'a, P, F, E, A, B>(parser: P, map_fn: F) -> impl Parser<'a, B>
|
||||
where
|
||||
P: Parser<'a, A>,
|
||||
F: Fn(A) -> std::result::Result<B, E>,
|
||||
{
|
||||
move |input| {
|
||||
parser.parse(input).and_then(|(next_input, result)| {
|
||||
if let Ok(res) = map_fn(result) {
|
||||
Ok((next_input, res))
|
||||
} else {
|
||||
Err(next_input)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn match_literal<'a>(expected: &'static str) -> impl Parser<'a, ()> {
|
||||
move |input: &'a str| match input.get(0..expected.len()) {
|
||||
Some(next) if next == expected => Ok((&input[expected.len()..], ())),
|
||||
|
@ -194,24 +178,6 @@ pub fn quoted_string<'a>() -> impl Parser<'a, String> {
|
|||
)
|
||||
}
|
||||
|
||||
pub fn quoted_slice<'a>() -> impl Parser<'a, &'a str> {
|
||||
move |input: &'a str| {
|
||||
if input.is_empty() || !input.starts_with('"') {
|
||||
return Err(input);
|
||||
}
|
||||
|
||||
let mut i = 1;
|
||||
while i < input.len() {
|
||||
if input[i..].starts_with('\"') && !input[i - 1..].starts_with('\\') {
|
||||
return Ok((&input[i + 1..], &input[1..i]));
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
|
||||
Err(input)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BoxedParser<'a, Output> {
|
||||
parser: Box<dyn Parser<'a, Output> + 'a>,
|
||||
}
|
||||
|
@ -251,8 +217,6 @@ where
|
|||
right(space0(), left(parser, space0()))
|
||||
}
|
||||
|
||||
pub use whitespace_wrap as ws_eat;
|
||||
|
||||
pub fn pair<'a, P1, P2, R1, R2>(parser1: P1, parser2: P2) -> impl Parser<'a, (R1, R2)>
|
||||
where
|
||||
P1: Parser<'a, R1>,
|
||||
|
@ -326,69 +290,6 @@ pub fn space0<'a>() -> impl Parser<'a, Vec<char>> {
|
|||
zero_or_more(whitespace_char())
|
||||
}
|
||||
|
||||
pub fn is_a<'a>(slice: &'static [u8]) -> impl Parser<'a, &'a str> {
|
||||
move |input: &'a str| {
|
||||
let mut i = 0;
|
||||
for byte in input.as_bytes().iter() {
|
||||
if !slice.contains(byte) {
|
||||
break;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
if i == 0 {
|
||||
return Err("");
|
||||
}
|
||||
let (b, a) = input.split_at(i);
|
||||
Ok((a, b))
|
||||
}
|
||||
}
|
||||
|
||||
/// Try alternative parsers in order until one succeeds.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use melib::parsec::{Parser, quoted_slice, match_literal, alt, delimited, prefix};
|
||||
///
|
||||
/// let parser = |input| {
|
||||
/// alt([
|
||||
/// 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(input2)
|
||||
/// );
|
||||
/// ```
|
||||
pub fn alt<'a, P, A, const N: usize>(parsers: [P; N]) -> impl Parser<'a, A>
|
||||
where
|
||||
P: Parser<'a, A>,
|
||||
{
|
||||
move |input| {
|
||||
for parser in parsers.iter() {
|
||||
if let Ok(res) = parser.parse(input) {
|
||||
return Ok(res);
|
||||
}
|
||||
}
|
||||
Err(input)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn and_then<'a, P, F, A, B, NextP>(parser: P, f: F) -> impl Parser<'a, B>
|
||||
where
|
||||
P: Parser<'a, A>,
|
||||
|
@ -444,198 +345,3 @@ where
|
|||
Ok((&input[offset..], input))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn separated_list0<'a, P, A, S, Sep>(
|
||||
parser: P,
|
||||
separator: S,
|
||||
terminated: bool,
|
||||
) -> impl Parser<'a, Vec<A>>
|
||||
where
|
||||
P: Parser<'a, A>,
|
||||
S: Parser<'a, Sep>,
|
||||
{
|
||||
move |mut input| {
|
||||
let mut result = Vec::new();
|
||||
let mut prev_sep_result = Ok(());
|
||||
let mut last_item_input = input;
|
||||
|
||||
while let Ok((next_input, next_item)) = parser.parse(input) {
|
||||
prev_sep_result?;
|
||||
input = next_input;
|
||||
last_item_input = next_input;
|
||||
result.push(next_item);
|
||||
match separator.parse(input) {
|
||||
Ok((next_input, _)) => {
|
||||
input = next_input;
|
||||
}
|
||||
Err(err) => {
|
||||
prev_sep_result = Err(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !terminated {
|
||||
input = last_item_input;
|
||||
}
|
||||
|
||||
Ok((input, result))
|
||||
}
|
||||
}
|
||||
|
||||
/// Take `count` bytes
|
||||
pub fn take<'a>(count: usize) -> impl Parser<'a, &'a str> {
|
||||
move |i: &'a str| {
|
||||
if i.len() < count || !i.is_char_boundary(count) {
|
||||
Err("")
|
||||
} else {
|
||||
let (b, a) = i.split_at(count);
|
||||
Ok((a, b))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Take a literal
|
||||
///
|
||||
///```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",
|
||||
/// ))
|
||||
/// );
|
||||
///```
|
||||
pub fn take_literal<'a, P>(parser: P) -> impl Parser<'a, &'a str>
|
||||
where
|
||||
P: Parser<'a, usize>,
|
||||
{
|
||||
move |input: &'a str| {
|
||||
let (rest, length) = parser.parse(input)?;
|
||||
take(length).parse(rest)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[test]
|
||||
fn test_parsec() {
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum JsonValue {
|
||||
JsonString(String),
|
||||
JsonNumber(f64),
|
||||
JsonBool(bool),
|
||||
JsonNull,
|
||||
JsonObject(HashMap<String, JsonValue>),
|
||||
JsonArray(Vec<JsonValue>),
|
||||
}
|
||||
|
||||
fn parse_value<'a>() -> impl Parser<'a, JsonValue> {
|
||||
move |input| {
|
||||
either(
|
||||
either(
|
||||
either(
|
||||
either(
|
||||
either(
|
||||
map(parse_bool(), |b| JsonValue::JsonBool(b)),
|
||||
map(parse_null(), |()| JsonValue::JsonNull),
|
||||
),
|
||||
map(parse_array(), |vec| JsonValue::JsonArray(vec)),
|
||||
),
|
||||
map(parse_object(), |obj| JsonValue::JsonObject(obj)),
|
||||
),
|
||||
map(parse_number(), |n| JsonValue::JsonNumber(n)),
|
||||
),
|
||||
map(quoted_string(), |s| JsonValue::JsonString(s)),
|
||||
)
|
||||
.parse(input)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_number<'a>() -> impl Parser<'a, f64> {
|
||||
move |input| {
|
||||
either(
|
||||
map(match_literal("TRUE"), |()| 1.0),
|
||||
map(match_literal("FALSe"), |()| 1.0),
|
||||
)
|
||||
.parse(input)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_bool<'a>() -> impl Parser<'a, bool> {
|
||||
move |input| {
|
||||
ws_eat(either(
|
||||
map(match_literal("true"), |()| true),
|
||||
map(match_literal("false"), |()| false),
|
||||
))
|
||||
.parse(input)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_null<'a>() -> impl Parser<'a, ()> {
|
||||
move |input| ws_eat(match_literal("null")).parse(input)
|
||||
}
|
||||
|
||||
fn parse_array<'a>() -> impl Parser<'a, Vec<JsonValue>> {
|
||||
move |input| {
|
||||
delimited(
|
||||
ws_eat(match_literal("[")),
|
||||
separated_list0(parse_value(), ws_eat(match_literal(",")), false),
|
||||
ws_eat(match_literal("]")),
|
||||
)
|
||||
.parse(input)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_object<'a>() -> impl Parser<'a, HashMap<String, JsonValue>> {
|
||||
move |input| {
|
||||
map(
|
||||
delimited(
|
||||
ws_eat(match_literal("{")),
|
||||
separated_list0(
|
||||
pair(
|
||||
suffix(quoted_string(), ws_eat(match_literal(":"))),
|
||||
parse_value(),
|
||||
),
|
||||
ws_eat(match_literal(",")),
|
||||
false,
|
||||
),
|
||||
ws_eat(match_literal("}")),
|
||||
),
|
||||
|vec: Vec<(String, JsonValue)>| vec.into_iter().collect(),
|
||||
)
|
||||
.parse(input)
|
||||
}
|
||||
}
|
||||
assert_eq!(
|
||||
Ok(("", JsonValue::JsonString("a".to_string()))),
|
||||
parse_value().parse(r#""a""#)
|
||||
);
|
||||
assert_eq!(
|
||||
Ok(("", JsonValue::JsonBool(true))),
|
||||
parse_value().parse(r#"true"#)
|
||||
);
|
||||
assert_eq!(
|
||||
Ok(("", JsonValue::JsonObject(HashMap::default()))),
|
||||
parse_value().parse(r#"{}"#)
|
||||
);
|
||||
println!("{:?}", parse_value().parse(r#"{"a":true}"#));
|
||||
println!("{:?}", parse_value().parse(r#"{"a":true,"b":false}"#));
|
||||
println!("{:?}", parse_value().parse(r#"{ "a" : true,"b": false }"#));
|
||||
println!("{:?}", parse_value().parse(r#"{ "a" : true,"b": false,}"#));
|
||||
println!("{:?}", parse_value().parse(r#"{"a":false,"b":false,}"#));
|
||||
// Line:0 Col:18 Error parsing object
|
||||
// { "a":1, "b" : 2, }
|
||||
// ^Unexpected ','
|
||||
}
|
||||
}
|
||||
|
|
|
@ -179,9 +179,7 @@ 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");
|
||||
/// 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), &"Subject");
|
||||
/// ```
|
||||
pub trait SubjectPrefix {
|
||||
const USUAL_PREFIXES: &'static [&'static str] = &[
|
||||
|
@ -281,7 +279,7 @@ pub trait SubjectPrefix {
|
|||
];
|
||||
fn is_a_reply(&self) -> bool;
|
||||
fn strip_prefixes(&mut self) -> &mut Self;
|
||||
fn strip_prefixes_from_list(&mut self, list: &[&str], times: Option<u8>) -> &mut Self;
|
||||
fn strip_prefixes_from_list(&mut self, list: &[&str]) -> &mut Self;
|
||||
}
|
||||
|
||||
impl SubjectPrefix for &[u8] {
|
||||
|
@ -337,10 +335,10 @@ impl SubjectPrefix for &[u8] {
|
|||
self
|
||||
}
|
||||
|
||||
fn strip_prefixes_from_list(&mut self, list: &[&str], mut times: Option<u8>) -> &mut Self {
|
||||
fn strip_prefixes_from_list(&mut self, list: &[&str]) -> &mut Self {
|
||||
let result = {
|
||||
let mut slice = self.trim();
|
||||
'outer: loop {
|
||||
loop {
|
||||
let len = slice.len();
|
||||
for prefix in list.iter() {
|
||||
if slice
|
||||
|
@ -349,14 +347,10 @@ impl SubjectPrefix for &[u8] {
|
|||
.unwrap_or(false)
|
||||
{
|
||||
slice = &slice[prefix.len()..];
|
||||
slice = slice.trim();
|
||||
times = times.map(|u| u.saturating_sub(1));
|
||||
if times == Some(0) {
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
slice = slice.trim();
|
||||
}
|
||||
if slice.len() == len || times == Some(0) {
|
||||
if slice.len() == len {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -414,10 +408,10 @@ impl SubjectPrefix for &str {
|
|||
self
|
||||
}
|
||||
|
||||
fn strip_prefixes_from_list(&mut self, list: &[&str], mut times: Option<u8>) -> &mut Self {
|
||||
fn strip_prefixes_from_list(&mut self, list: &[&str]) -> &mut Self {
|
||||
let result = {
|
||||
let mut slice = self.trim();
|
||||
'outer: loop {
|
||||
loop {
|
||||
let len = slice.len();
|
||||
for prefix in list.iter() {
|
||||
if slice
|
||||
|
@ -426,14 +420,10 @@ impl SubjectPrefix for &str {
|
|||
.unwrap_or(false)
|
||||
{
|
||||
slice = &slice[prefix.len()..];
|
||||
slice = slice.trim();
|
||||
times = times.map(|u| u.saturating_sub(1));
|
||||
if times == Some(0) {
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
slice = slice.trim();
|
||||
}
|
||||
if slice.len() == len || times == Some(0) {
|
||||
if slice.len() == len {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -228,21 +228,21 @@ impl Composer {
|
|||
.as_ref()
|
||||
.map(|v| v.iter().map(String::as_str).collect::<Vec<&str>>())
|
||||
.unwrap_or_default();
|
||||
let subject_stripped = subject.as_ref().strip_prefixes_from_list(
|
||||
if prefix_list.is_empty() {
|
||||
let subject = subject
|
||||
.as_ref()
|
||||
.strip_prefixes_from_list(if prefix_list.is_empty() {
|
||||
<&str>::USUAL_PREFIXES
|
||||
} else {
|
||||
&prefix_list
|
||||
},
|
||||
Some(1),
|
||||
) == &subject.as_ref();
|
||||
})
|
||||
.to_string();
|
||||
|
||||
let prefix =
|
||||
account_settings!(context[ret.account_hash].composing.reply_prefix).as_str();
|
||||
if subject_stripped {
|
||||
if !subject.starts_with(prefix) {
|
||||
format!("{prefix} {subject}", prefix = prefix, subject = subject)
|
||||
} else {
|
||||
subject.to_string()
|
||||
subject
|
||||
}
|
||||
};
|
||||
ret.draft.set_header("Subject", subject);
|
||||
|
@ -2390,61 +2390,3 @@ fn attribution_string(
|
|||
);
|
||||
melib::datetime::timestamp_to_string(date, Some(fmt.as_str()), posix)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compose_reply_subject_prefix() {
|
||||
let raw_mail = r#"From: "some name" <some@example.com>
|
||||
To: "me" <myself@example.com>
|
||||
Cc:
|
||||
Subject: RE: your e-mail
|
||||
Message-ID: <h2g7f.z0gy2pgaen5m@example.com>
|
||||
Content-Type: text/plain
|
||||
|
||||
hello world.
|
||||
"#;
|
||||
|
||||
let envelope = Envelope::from_bytes(raw_mail.as_bytes(), None).expect("Could not parse mail");
|
||||
let mut context = Context::new_mock();
|
||||
let account_hash = context.accounts[0].hash();
|
||||
let mailbox_hash = 0;
|
||||
let envelope_hash = envelope.hash();
|
||||
context.accounts[0]
|
||||
.collection
|
||||
.insert(envelope, mailbox_hash);
|
||||
let composer = Composer::reply_to(
|
||||
(account_hash, mailbox_hash, envelope_hash),
|
||||
String::new(),
|
||||
&mut context,
|
||||
false,
|
||||
);
|
||||
assert_eq!(&composer.draft.headers()["Subject"], "RE: your e-mail");
|
||||
assert_eq!(
|
||||
&composer.draft.headers()["To"],
|
||||
r#"some name <some@example.com>"#
|
||||
);
|
||||
let raw_mail = r#"From: "some name" <some@example.com>
|
||||
To: "me" <myself@example.com>
|
||||
Cc:
|
||||
Subject: your e-mail
|
||||
Message-ID: <h2g7f.z0gy2pgaen5m@example.com>
|
||||
Content-Type: text/plain
|
||||
|
||||
hello world.
|
||||
"#;
|
||||
let envelope = Envelope::from_bytes(raw_mail.as_bytes(), None).expect("Could not parse mail");
|
||||
let envelope_hash = envelope.hash();
|
||||
context.accounts[0]
|
||||
.collection
|
||||
.insert(envelope, mailbox_hash);
|
||||
let composer = Composer::reply_to(
|
||||
(account_hash, mailbox_hash, envelope_hash),
|
||||
String::new(),
|
||||
&mut context,
|
||||
false,
|
||||
);
|
||||
assert_eq!(&composer.draft.headers()["Subject"], "Re: your e-mail");
|
||||
assert_eq!(
|
||||
&composer.draft.headers()["To"],
|
||||
r#"some name <some@example.com>"#
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2223,13 +2223,13 @@ impl Component for MailView {
|
|||
None,
|
||||
true,
|
||||
);
|
||||
let exec_cmd = desktop_exec_to_command(
|
||||
let (exec_cmd, argument) = desktop_exec_to_command(
|
||||
&command,
|
||||
p.path.display().to_string(),
|
||||
false,
|
||||
);
|
||||
match Command::new("sh")
|
||||
.args(&["-c", &exec_cmd])
|
||||
match Command::new(&exec_cmd)
|
||||
.arg(&argument)
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()
|
||||
|
@ -2241,8 +2241,8 @@ impl Component for MailView {
|
|||
Err(err) => {
|
||||
context.replies.push_back(UIEvent::StatusEvent(
|
||||
StatusEvent::DisplayMessage(format!(
|
||||
"Failed to start `{}`: {}",
|
||||
&exec_cmd, err
|
||||
"Failed to start `{} {}`: {}",
|
||||
&exec_cmd, &argument, err
|
||||
)),
|
||||
));
|
||||
}
|
||||
|
@ -2790,66 +2790,49 @@ fn save_attachment(path: &std::path::Path, bytes: &[u8]) -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn desktop_exec_to_command(command: &str, path: String, is_url: bool) -> String {
|
||||
fn desktop_exec_to_command(command: &str, path: String, is_url: bool) -> (String, String) {
|
||||
/* Purge unused field codes */
|
||||
let command = command
|
||||
.replace("%i", "")
|
||||
.replace("%c", "")
|
||||
.replace("%k", "");
|
||||
if command.contains("%f") {
|
||||
command.replacen("%f", &path.replace(' ', "\\ "), 1)
|
||||
} else if command.contains("%F") {
|
||||
command.replacen("%F", &path.replace(' ', "\\ "), 1)
|
||||
} else if command.contains("%u") || command.contains("%U") {
|
||||
let from_pattern = if command.contains("%u") { "%u" } else { "%U" };
|
||||
if let Some(pos) = command.find("%f").or_else(|| command.find("%F")) {
|
||||
(command[0..pos].trim().to_string(), path)
|
||||
} else if let Some(pos) = command.find("%u").or_else(|| command.find("%U")) {
|
||||
if is_url {
|
||||
command.replacen(from_pattern, &path, 1)
|
||||
(command[0..pos].trim().to_string(), path)
|
||||
} else {
|
||||
command.replacen(
|
||||
from_pattern,
|
||||
&format!("file://{}", path).replace(' ', "\\ "),
|
||||
1,
|
||||
(
|
||||
command[0..pos].trim().to_string(),
|
||||
format!("file://{}", path),
|
||||
)
|
||||
}
|
||||
} else if is_url {
|
||||
format!("{} {}", command, path)
|
||||
} else {
|
||||
format!("{} {}", command, path.replace(' ', "\\ "))
|
||||
(command, path)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
#[test]
|
||||
fn test_desktop_exec() {
|
||||
assert_eq!(
|
||||
"ristretto /tmp/file".to_string(),
|
||||
desktop_exec_to_command("ristretto %F", "/tmp/file".to_string(), false)
|
||||
);
|
||||
assert_eq!(
|
||||
"/usr/lib/firefox-esr/firefox-esr file:///tmp/file".to_string(),
|
||||
desktop_exec_to_command(
|
||||
"/usr/lib/firefox-esr/firefox-esr %u",
|
||||
"/tmp/file".to_string(),
|
||||
false
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
"/usr/lib/firefox-esr/firefox-esr www.example.com".to_string(),
|
||||
desktop_exec_to_command(
|
||||
"/usr/lib/firefox-esr/firefox-esr %u",
|
||||
"www.example.com".to_string(),
|
||||
true
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
"/usr/bin/vlc --started-from-file www.example.com".to_string(),
|
||||
desktop_exec_to_command(
|
||||
"/usr/bin/vlc --started-from-file %U",
|
||||
"www.example.com".to_string(),
|
||||
true
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
"zathura --fork file:///tmp/file".to_string(),
|
||||
desktop_exec_to_command("zathura --fork %U", "file:///tmp/file".to_string(), true)
|
||||
);
|
||||
for cmd in [
|
||||
"ristretto %F",
|
||||
"/usr/lib/firefox-esr/firefox-esr %u",
|
||||
"/usr/bin/vlc --started-from-file %U",
|
||||
"zathura %U",
|
||||
]
|
||||
.iter()
|
||||
{
|
||||
println!(
|
||||
"cmd = {} output = {:?}, is_url = false",
|
||||
cmd,
|
||||
desktop_exec_to_command(cmd, "/tmp/file".to_string(), false)
|
||||
);
|
||||
println!(
|
||||
"cmd = {} output = {:?}, is_url = true",
|
||||
cmd,
|
||||
desktop_exec_to_command(cmd, "www.example.com".to_string(), true)
|
||||
);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
|
|
@ -403,13 +403,13 @@ impl Component for EnvelopeView {
|
|||
None,
|
||||
true,
|
||||
);
|
||||
let exec_cmd = super::desktop_exec_to_command(
|
||||
let (exec_cmd, argument) = super::desktop_exec_to_command(
|
||||
&command,
|
||||
p.path.display().to_string(),
|
||||
false,
|
||||
);
|
||||
match Command::new("sh")
|
||||
.args(&["-c", &exec_cmd])
|
||||
match Command::new(&exec_cmd)
|
||||
.arg(&argument)
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()
|
||||
|
@ -421,8 +421,8 @@ impl Component for EnvelopeView {
|
|||
Err(err) => {
|
||||
context.replies.push_back(UIEvent::StatusEvent(
|
||||
StatusEvent::DisplayMessage(format!(
|
||||
"Failed to start `{}`: {}",
|
||||
&exec_cmd, err
|
||||
"Failed to start `{} {}`: {}",
|
||||
&exec_cmd, &argument, err
|
||||
)),
|
||||
));
|
||||
}
|
||||
|
|
|
@ -151,10 +151,10 @@ impl Component for HtmlView {
|
|||
};
|
||||
if let Some(command) = command {
|
||||
let p = create_temp_file(&self.bytes, None, None, true);
|
||||
let exec_cmd =
|
||||
let (exec_cmd, argument) =
|
||||
super::desktop_exec_to_command(&command, p.path.display().to_string(), false);
|
||||
match Command::new("sh")
|
||||
.args(&["-c", &exec_cmd])
|
||||
match Command::new(&exec_cmd)
|
||||
.arg(&argument)
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()
|
||||
|
@ -166,8 +166,8 @@ impl Component for HtmlView {
|
|||
Err(err) => {
|
||||
context.replies.push_back(UIEvent::StatusEvent(
|
||||
StatusEvent::DisplayMessage(format!(
|
||||
"Failed to start `{}`: {}",
|
||||
&exec_cmd, err
|
||||
"Failed to start `{} {}`: {}",
|
||||
&exec_cmd, &argument, err
|
||||
)),
|
||||
));
|
||||
}
|
||||
|
|
30
src/conf.rs
30
src/conf.rs
|
@ -155,24 +155,24 @@ impl FileMailboxConf {
|
|||
use crate::conf::deserializers::extra_settings;
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct FileAccount {
|
||||
pub root_mailbox: String,
|
||||
pub format: String,
|
||||
pub identity: String,
|
||||
root_mailbox: String,
|
||||
format: String,
|
||||
identity: String,
|
||||
#[serde(default)]
|
||||
pub extra_identities: Vec<String>,
|
||||
extra_identities: Vec<String>,
|
||||
#[serde(default = "none")]
|
||||
pub display_name: Option<String>,
|
||||
display_name: Option<String>,
|
||||
|
||||
#[serde(default = "false_val")]
|
||||
pub read_only: bool,
|
||||
read_only: bool,
|
||||
#[serde(default)]
|
||||
pub subscribed_mailboxes: Vec<String>,
|
||||
subscribed_mailboxes: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub mailboxes: IndexMap<String, FileMailboxConf>,
|
||||
mailboxes: IndexMap<String, FileMailboxConf>,
|
||||
#[serde(default)]
|
||||
pub search_backend: SearchBackend,
|
||||
search_backend: SearchBackend,
|
||||
#[serde(default)]
|
||||
pub order: (SortField, SortOrder),
|
||||
order: (SortField, SortOrder),
|
||||
#[serde(default = "false_val")]
|
||||
pub manual_refresh: bool,
|
||||
#[serde(default = "none")]
|
||||
|
@ -345,22 +345,18 @@ impl FileSettings {
|
|||
if path_string.is_empty() {
|
||||
return Err(MeliError::new("No configuration found."));
|
||||
}
|
||||
#[cfg(not(test))]
|
||||
let ask = Ask {
|
||||
message: format!(
|
||||
"No configuration found. Would you like to generate one in {}?",
|
||||
path_string
|
||||
),
|
||||
};
|
||||
#[cfg(not(test))]
|
||||
if ask.run() {
|
||||
create_config_file(&config_path)?;
|
||||
return Err(MeliError::new(
|
||||
"Edit the sample configuration and relaunch meli.",
|
||||
));
|
||||
}
|
||||
#[cfg(test)]
|
||||
return Ok(FileSettings::default());
|
||||
return Err(MeliError::new("No configuration file found."));
|
||||
}
|
||||
|
||||
|
@ -924,9 +920,9 @@ mod pp {
|
|||
#[serde(deny_unknown_fields)]
|
||||
pub struct LogSettings {
|
||||
#[serde(default)]
|
||||
pub log_file: Option<PathBuf>,
|
||||
log_file: Option<PathBuf>,
|
||||
#[serde(default)]
|
||||
pub maximum_level: melib::LoggingLevel,
|
||||
maximum_level: melib::LoggingLevel,
|
||||
}
|
||||
|
||||
pub use dotaddressable::*;
|
||||
|
@ -1265,7 +1261,7 @@ send_mail = '/bin/false'
|
|||
.write_all("[composing]\nsend_mail = '/bin/false'\n".as_bytes())
|
||||
.unwrap();
|
||||
let err = FileSettings::validate(new_file.path.clone(), false, true).unwrap_err();
|
||||
assert_eq!(err.summary.as_ref(), "Configuration error (account-name): root_mailbox `/path/to/root/mailbox` is not a valid directory.");
|
||||
assert_eq!(err.summary.as_ref(), "Configuration error (account-name): root_path `/path/to/root/mailbox` is not a valid directory.");
|
||||
|
||||
/* Test unrecognised configuration entries error */
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
|
||||
use super::{AccountConf, FileMailboxConf};
|
||||
use crate::jobs::{JobExecutor, JobId, JoinHandle};
|
||||
use crate::RateLimit;
|
||||
use indexmap::IndexMap;
|
||||
use melib::backends::*;
|
||||
use melib::email::*;
|
||||
|
@ -130,11 +131,47 @@ impl MailboxEntry {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum IsOnline {
|
||||
Unitialized {
|
||||
last_activity: std::time::Instant,
|
||||
rate_limit: RateLimit,
|
||||
},
|
||||
Yes {
|
||||
last_activity: std::time::Instant,
|
||||
},
|
||||
No {
|
||||
last_activity: std::time::Instant,
|
||||
err: MeliError,
|
||||
},
|
||||
NoWillRetry {
|
||||
last_activity: std::time::Instant,
|
||||
err: MeliError,
|
||||
rate_limit: RateLimit,
|
||||
},
|
||||
}
|
||||
|
||||
impl IsOnline {
|
||||
#[inline(always)]
|
||||
pub fn as_err(&self) -> Option<&MeliError> {
|
||||
if let IsOnline::No { ref err, .. } | IsOnline::NoWillRetry { ref err, .. } = self {
|
||||
Some(err)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn is_ok(&self) -> bool {
|
||||
matches!(&self, IsOnline::Yes { .. })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Account {
|
||||
name: String,
|
||||
hash: AccountHash,
|
||||
pub is_online: Result<()>,
|
||||
pub is_online: IsOnline,
|
||||
pub(crate) mailbox_entries: IndexMap<MailboxHash, MailboxEntry>,
|
||||
pub(crate) mailboxes_order: Vec<MailboxHash>,
|
||||
tree: Vec<MailboxNode>,
|
||||
|
@ -338,6 +375,10 @@ impl core::fmt::Display for JobRequest {
|
|||
}
|
||||
|
||||
impl JobRequest {
|
||||
pub fn is_mailboxes(&self) -> bool {
|
||||
matches!(self, JobRequest::Mailboxes { .. })
|
||||
}
|
||||
|
||||
pub fn is_watch(&self) -> bool {
|
||||
matches!(self, JobRequest::Watch { .. })
|
||||
}
|
||||
|
@ -518,9 +559,14 @@ impl Account {
|
|||
hash,
|
||||
name,
|
||||
is_online: if !backend.capabilities().is_remote {
|
||||
Ok(())
|
||||
IsOnline::Yes {
|
||||
last_activity: std::time::Instant::now(),
|
||||
}
|
||||
} else {
|
||||
Err(MeliError::new("Attempting connection."))
|
||||
IsOnline::Unitialized {
|
||||
last_activity: std::time::Instant::now(),
|
||||
rate_limit: RateLimit::new(1, 1, job_executor.clone()).exponential_backoff(5),
|
||||
}
|
||||
},
|
||||
mailbox_entries: Default::default(),
|
||||
mailboxes_order: Default::default(),
|
||||
|
@ -1555,36 +1601,25 @@ impl Account {
|
|||
|
||||
/* Call only in Context::is_online, since only Context can launch the watcher threads if an
|
||||
* account goes from offline to online. */
|
||||
pub fn is_online(&mut self) -> Result<()> {
|
||||
if !self.backend_capabilities.is_remote && !self.backend_capabilities.is_async {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if self.is_online.is_err()
|
||||
&& self
|
||||
.is_online
|
||||
.as_ref()
|
||||
.unwrap_err()
|
||||
.kind
|
||||
.is_authentication()
|
||||
{
|
||||
return self.is_online.clone();
|
||||
}
|
||||
if self.is_online.is_ok() {
|
||||
return Ok(());
|
||||
}
|
||||
if !self.active_jobs.values().any(JobRequest::is_online) {
|
||||
let online_job = self.backend.read().unwrap().is_online();
|
||||
if let Ok(online_job) = online_job {
|
||||
let handle = if self.backend_capabilities.is_async {
|
||||
self.job_executor.spawn_specialized(online_job)
|
||||
} else {
|
||||
self.job_executor.spawn_blocking(online_job)
|
||||
};
|
||||
self.insert_job(handle.job_id, JobRequest::IsOnline { handle });
|
||||
pub fn is_online(&mut self) -> &IsOnline {
|
||||
match &self.is_online {
|
||||
_ if !self.backend_capabilities.is_remote && !self.backend_capabilities.is_async => {}
|
||||
IsOnline::Unitialized { .. } | IsOnline::No { .. } | IsOnline::Yes { .. } => {}
|
||||
IsOnline::NoWillRetry { .. }
|
||||
if self.active_jobs.values().any(JobRequest::is_online) => {}
|
||||
IsOnline::NoWillRetry { .. } => {
|
||||
let online_job = self.backend.read().unwrap().is_online();
|
||||
if let Ok(online_job) = online_job {
|
||||
let handle = if self.backend_capabilities.is_async {
|
||||
self.job_executor.spawn_specialized(online_job)
|
||||
} else {
|
||||
self.job_executor.spawn_blocking(online_job)
|
||||
};
|
||||
self.insert_job(handle.job_id, JobRequest::IsOnline { handle });
|
||||
}
|
||||
}
|
||||
}
|
||||
self.is_online.clone()
|
||||
&self.is_online
|
||||
}
|
||||
|
||||
pub fn search(
|
||||
|
@ -1632,6 +1667,135 @@ impl Account {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn update_status(&mut self, result: Result<()>) {
|
||||
if let Err(err) = result {
|
||||
if err.kind.is_authentication() {
|
||||
self.sender
|
||||
.send(ThreadEvent::UIEvent(UIEvent::Notification(
|
||||
Some(format!("{}: authentication error", &self.name)),
|
||||
err.to_string(),
|
||||
Some(crate::types::NotificationType::Error(err.kind)),
|
||||
)))
|
||||
.expect("Could not send event on main channel");
|
||||
self.is_online = IsOnline::No {
|
||||
last_activity: std::time::Instant::now(),
|
||||
err,
|
||||
};
|
||||
return;
|
||||
}
|
||||
if let IsOnline::NoWillRetry {
|
||||
ref mut last_activity,
|
||||
err: ref mut err_ptr,
|
||||
ref mut rate_limit,
|
||||
} = self.is_online
|
||||
{
|
||||
if rate_limit.active {
|
||||
*last_activity = std::time::Instant::now();
|
||||
*err_ptr = err;
|
||||
} else {
|
||||
self.is_online = IsOnline::No {
|
||||
last_activity: std::time::Instant::now(),
|
||||
err,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
self.is_online = IsOnline::NoWillRetry {
|
||||
last_activity: std::time::Instant::now(),
|
||||
err,
|
||||
rate_limit: RateLimit::new(1, 1, self.job_executor.clone())
|
||||
.exponential_backoff(5),
|
||||
};
|
||||
}
|
||||
} else {
|
||||
if self.mailbox_entries.is_empty() {
|
||||
let backend = self.backend.read().unwrap();
|
||||
if let Ok(mailboxes_job) = backend.mailboxes() {
|
||||
if let Ok(online_job) = backend.is_online() {
|
||||
let handle = if backend.capabilities().is_async {
|
||||
self.job_executor
|
||||
.spawn_specialized(online_job.then(|_| mailboxes_job))
|
||||
} else {
|
||||
self.job_executor
|
||||
.spawn_blocking(online_job.then(|_| mailboxes_job))
|
||||
};
|
||||
let job_id = handle.job_id;
|
||||
self.active_jobs
|
||||
.insert(job_id, JobRequest::Mailboxes { handle });
|
||||
self.active_job_instants
|
||||
.insert(std::time::Instant::now(), job_id);
|
||||
self.sender
|
||||
.send(ThreadEvent::UIEvent(UIEvent::StatusEvent(
|
||||
StatusEvent::NewJob(job_id),
|
||||
)))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
self.is_online = IsOnline::Unitialized {
|
||||
last_activity: std::time::Instant::now(),
|
||||
rate_limit: RateLimit::new(1, 1, self.job_executor.clone())
|
||||
.exponential_backoff(5),
|
||||
};
|
||||
} else {
|
||||
if let IsOnline::Yes {
|
||||
ref mut last_activity,
|
||||
} = self.is_online
|
||||
{
|
||||
*last_activity = std::time::Instant::now();
|
||||
} else {
|
||||
self.is_online = IsOnline::Yes {
|
||||
last_activity: std::time::Instant::now(),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
self.sender
|
||||
.send(ThreadEvent::UIEvent(UIEvent::AccountStatusChange(
|
||||
self.hash, None,
|
||||
)))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn retry_mailboxes(&mut self) {
|
||||
match self.is_online {
|
||||
IsOnline::NoWillRetry {
|
||||
ref rate_limit,
|
||||
ref err,
|
||||
last_activity,
|
||||
} if !rate_limit.active => {
|
||||
self.is_online = IsOnline::No {
|
||||
err: err.clone(),
|
||||
last_activity,
|
||||
};
|
||||
return;
|
||||
}
|
||||
IsOnline::NoWillRetry { .. } | IsOnline::Unitialized { .. } => {}
|
||||
_ => return,
|
||||
}
|
||||
|
||||
let backend = self.backend.read().unwrap();
|
||||
if let Ok(mailboxes_job) = backend.mailboxes() {
|
||||
if let Ok(online_job) = backend.is_online() {
|
||||
let handle = if backend.capabilities().is_async {
|
||||
self.job_executor
|
||||
.spawn_specialized(online_job.then(|_| mailboxes_job))
|
||||
} else {
|
||||
self.job_executor
|
||||
.spawn_blocking(online_job.then(|_| mailboxes_job))
|
||||
};
|
||||
let job_id = handle.job_id;
|
||||
self.active_jobs
|
||||
.insert(job_id, JobRequest::Mailboxes { handle });
|
||||
self.active_job_instants
|
||||
.insert(std::time::Instant::now(), job_id);
|
||||
self.sender
|
||||
.send(ThreadEvent::UIEvent(UIEvent::StatusEvent(
|
||||
StatusEvent::NewJob(job_id),
|
||||
)))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_event(&mut self, job_id: &JobId) -> bool {
|
||||
self.sender
|
||||
.send(ThreadEvent::UIEvent(UIEvent::StatusEvent(
|
||||
|
@ -1644,26 +1808,8 @@ impl Account {
|
|||
JobRequest::Mailboxes { ref mut handle } => {
|
||||
if let Ok(Some(mailboxes)) = handle.chan.try_recv() {
|
||||
if let Err(err) = mailboxes.and_then(|mailboxes| self.init(mailboxes)) {
|
||||
if err.kind.is_authentication() {
|
||||
self.sender
|
||||
.send(ThreadEvent::UIEvent(UIEvent::Notification(
|
||||
Some(format!("{}: authentication error", &self.name)),
|
||||
err.to_string(),
|
||||
Some(crate::types::NotificationType::Error(err.kind)),
|
||||
)))
|
||||
.expect("Could not send event on main channel");
|
||||
self.is_online = Err(err);
|
||||
return true;
|
||||
}
|
||||
let mailboxes_job = self.backend.read().unwrap().mailboxes();
|
||||
if let Ok(mailboxes_job) = mailboxes_job {
|
||||
let handle = if self.backend_capabilities.is_async {
|
||||
self.job_executor.spawn_specialized(mailboxes_job)
|
||||
} else {
|
||||
self.job_executor.spawn_blocking(mailboxes_job)
|
||||
};
|
||||
self.insert_job(handle.job_id, JobRequest::Mailboxes { handle });
|
||||
};
|
||||
self.update_status(Err(err));
|
||||
return true;
|
||||
} else {
|
||||
self.sender
|
||||
.send(ThreadEvent::UIEvent(UIEvent::AccountStatusChange(
|
||||
|
@ -1770,20 +1916,13 @@ impl Account {
|
|||
)))
|
||||
.unwrap();
|
||||
if is_online.is_ok() {
|
||||
if self.is_online.is_err()
|
||||
&& !self
|
||||
.is_online
|
||||
.as_ref()
|
||||
.unwrap_err()
|
||||
.kind
|
||||
.is_authentication()
|
||||
{
|
||||
if self.is_online.as_err().is_none() {
|
||||
self.watch();
|
||||
}
|
||||
self.is_online = Ok(());
|
||||
self.update_status(Ok(()));
|
||||
return true;
|
||||
}
|
||||
self.is_online = is_online;
|
||||
self.update_status(is_online);
|
||||
}
|
||||
let online_job = self.backend.read().unwrap().is_online();
|
||||
if let Ok(online_job) = online_job {
|
||||
|
@ -1800,31 +1939,10 @@ impl Account {
|
|||
Err(_) => { /* canceled */ }
|
||||
Ok(None) => {}
|
||||
Ok(Some(Ok(()))) => {
|
||||
if self.is_online.is_err()
|
||||
&& !self
|
||||
.is_online
|
||||
.as_ref()
|
||||
.unwrap_err()
|
||||
.kind
|
||||
.is_authentication()
|
||||
{
|
||||
self.update_status(Ok(()));
|
||||
if self.is_online.as_err().is_none() {
|
||||
self.watch();
|
||||
}
|
||||
if !(self.is_online.is_err()
|
||||
&& self
|
||||
.is_online
|
||||
.as_ref()
|
||||
.unwrap_err()
|
||||
.kind
|
||||
.is_authentication())
|
||||
{
|
||||
self.is_online = Ok(());
|
||||
self.sender
|
||||
.send(ThreadEvent::UIEvent(UIEvent::AccountStatusChange(
|
||||
self.hash, None,
|
||||
)))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
Ok(Some(Err(err))) => {
|
||||
if !err.kind.is_authentication() {
|
||||
|
@ -1838,7 +1956,7 @@ impl Account {
|
|||
self.insert_job(handle.job_id, JobRequest::IsOnline { handle });
|
||||
};
|
||||
}
|
||||
self.is_online = Err(err);
|
||||
self.update_status(Err(err));
|
||||
self.sender
|
||||
.send(ThreadEvent::UIEvent(UIEvent::AccountStatusChange(
|
||||
self.hash, None,
|
||||
|
|
|
@ -214,8 +214,7 @@ impl MailcapEntry {
|
|||
std::borrow::Cow::from("less")
|
||||
};
|
||||
|
||||
let mut pager = Command::new("sh")
|
||||
.args(["-c", pager_cmd.as_ref()])
|
||||
let mut pager = Command::new(pager_cmd.as_ref())
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::inherit())
|
||||
.spawn()?;
|
||||
|
|
13
src/main.rs
13
src/main.rs
|
@ -233,13 +233,12 @@ fn run_app(opt: Opt) -> Result<()> {
|
|||
}
|
||||
|
||||
use std::process::{Command, Stdio};
|
||||
let mut handle = Command::new("sh")
|
||||
.arg("-c")
|
||||
.arg(std::env::var("PAGER").unwrap_or_else(|_| "more".to_string()))
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::inherit())
|
||||
.stderr(Stdio::inherit())
|
||||
.spawn()?;
|
||||
let mut handle =
|
||||
Command::new(std::env::var("PAGER").unwrap_or_else(|_| "more".to_string()))
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::inherit())
|
||||
.stderr(Stdio::inherit())
|
||||
.spawn()?;
|
||||
handle.stdin.take().unwrap().write_all(v.as_bytes())?;
|
||||
handle.wait()?;
|
||||
|
||||
|
|
|
@ -24,15 +24,17 @@ extern crate melib;
|
|||
use melib::*;
|
||||
|
||||
use std::collections::VecDeque;
|
||||
extern crate xdg_utils;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
extern crate linkify;
|
||||
extern crate uuid;
|
||||
|
||||
extern crate serde_json;
|
||||
extern crate smallvec;
|
||||
extern crate termion;
|
||||
|
||||
use melib::backends::imap::managesieve::ManageSieveConnection;
|
||||
use melib::backends::imap::managesieve::new_managesieve_connection;
|
||||
use melib::Result;
|
||||
|
||||
#[macro_use]
|
||||
|
@ -62,7 +64,7 @@ pub mod sqlite3;
|
|||
|
||||
pub mod jobs;
|
||||
pub mod mailcap;
|
||||
//pub mod plugins;
|
||||
pub mod plugins;
|
||||
|
||||
use futures::executor::block_on;
|
||||
|
||||
|
@ -82,7 +84,10 @@ fn main() -> Result<()> {
|
|||
std::process::exit(1);
|
||||
}
|
||||
|
||||
let (config_path, account_name) = (std::mem::take(&mut args[0]), std::mem::take(&mut args[1]));
|
||||
let (config_path, account_name) = (
|
||||
std::mem::replace(&mut args[0], String::new()),
|
||||
std::mem::replace(&mut args[1], String::new()),
|
||||
);
|
||||
std::env::set_var("MELI_CONFIG", config_path);
|
||||
let settings = conf::Settings::new()?;
|
||||
if !settings.accounts.contains_key(&account_name) {
|
||||
|
@ -97,47 +102,12 @@ fn main() -> Result<()> {
|
|||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
let account = &settings.accounts[&account_name].account;
|
||||
let mut conn = ManageSieveConnection::new(
|
||||
0,
|
||||
account_name.clone(),
|
||||
account,
|
||||
melib::backends::BackendEventConsumer::new(std::sync::Arc::new(|_, _| {})),
|
||||
)?;
|
||||
block_on(conn.inner.connect())?;
|
||||
let mut conn = new_managesieve_connection(&settings.accounts[&account_name].account)?;
|
||||
block_on(conn.connect())?;
|
||||
let mut res = String::with_capacity(8 * 1024);
|
||||
|
||||
let mut input = String::new();
|
||||
const AVAILABLE_COMMANDS: &[&str] = &[
|
||||
"help",
|
||||
"logout",
|
||||
"listscripts",
|
||||
"checkscript",
|
||||
"putscript",
|
||||
"setactive",
|
||||
"getscript",
|
||||
"deletescript",
|
||||
];
|
||||
const COMMANDS_HELP: &[&str] = &[
|
||||
"help",
|
||||
"logout",
|
||||
"listscripts and whether they are active",
|
||||
"paste a script to check for validity without uploading it",
|
||||
"upload a script",
|
||||
"set a script as active",
|
||||
"get a script by its name",
|
||||
"delete a script by its name",
|
||||
];
|
||||
println!("managesieve shell: use 'help' for available commands");
|
||||
enum PrevCmd {
|
||||
None,
|
||||
Checkscript,
|
||||
PutscriptName,
|
||||
PutscriptString(String),
|
||||
SetActiveName,
|
||||
GetScriptName,
|
||||
}
|
||||
use PrevCmd::*;
|
||||
let mut prev_cmd: PrevCmd = None;
|
||||
println!("managesieve shell: use 'logout'");
|
||||
loop {
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
|
@ -146,85 +116,12 @@ fn main() -> Result<()> {
|
|||
io::stdout().flush().unwrap();
|
||||
match io::stdin().read_line(&mut input) {
|
||||
Ok(_) => {
|
||||
let input = input.trim();
|
||||
if input.eq_ignore_ascii_case("logout") {
|
||||
if input.trim().eq_ignore_ascii_case("logout") {
|
||||
break;
|
||||
}
|
||||
if input.eq_ignore_ascii_case("help") {
|
||||
println!("available commands: [{}]", AVAILABLE_COMMANDS.join(", "));
|
||||
continue;
|
||||
}
|
||||
if input.len() >= "help ".len()
|
||||
&& input[0.."help ".len()].eq_ignore_ascii_case("help ")
|
||||
{
|
||||
if let Some(i) = AVAILABLE_COMMANDS
|
||||
.iter()
|
||||
.position(|cmd| cmd.eq_ignore_ascii_case(&input["help ".len()..]))
|
||||
{
|
||||
println!("{}", COMMANDS_HELP[i]);
|
||||
} else {
|
||||
println!("invalid command `{}`", &input["help ".len()..]);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if input.eq_ignore_ascii_case("listscripts") {
|
||||
let scripts = block_on(conn.listscripts())?;
|
||||
println!("Got {} scripts:", scripts.len());
|
||||
for (script, active) in scripts {
|
||||
println!(
|
||||
"{}active: {}",
|
||||
if active { "" } else { "in" },
|
||||
String::from_utf8_lossy(&script)
|
||||
);
|
||||
}
|
||||
} else if input.eq_ignore_ascii_case("checkscript") {
|
||||
prev_cmd = Checkscript;
|
||||
println!("insert file path of script");
|
||||
} else if input.eq_ignore_ascii_case("putscript") {
|
||||
prev_cmd = PutscriptName;
|
||||
println!("Insert script name");
|
||||
} else if input.eq_ignore_ascii_case("setactive") {
|
||||
prev_cmd = SetActiveName;
|
||||
} else if input.eq_ignore_ascii_case("getscript") {
|
||||
prev_cmd = GetScriptName;
|
||||
} else if input.eq_ignore_ascii_case("deletescript") {
|
||||
println!("unimplemented `{}`", input);
|
||||
} else {
|
||||
match prev_cmd {
|
||||
None => println!("invalid command `{}`", input),
|
||||
Checkscript => {
|
||||
let content = std::fs::read_to_string(&input).unwrap();
|
||||
let result = block_on(conn.checkscript(content.as_bytes()));
|
||||
println!("Got {:?}", result);
|
||||
prev_cmd = None;
|
||||
}
|
||||
PutscriptName => {
|
||||
prev_cmd = PutscriptString(input.to_string());
|
||||
println!("insert file path of script");
|
||||
}
|
||||
PutscriptString(name) => {
|
||||
prev_cmd = None;
|
||||
let content = std::fs::read_to_string(&input).unwrap();
|
||||
let result =
|
||||
block_on(conn.putscript(name.as_bytes(), content.as_bytes()));
|
||||
println!("Got {:?}", result);
|
||||
}
|
||||
SetActiveName => {
|
||||
prev_cmd = None;
|
||||
let result = block_on(conn.setactive(input.as_bytes()));
|
||||
println!("Got {:?}", result);
|
||||
}
|
||||
GetScriptName => {
|
||||
prev_cmd = None;
|
||||
let result = block_on(conn.getscript(input.as_bytes()));
|
||||
println!("Got {:?}", result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//block_on(conn.send_command(input.as_bytes()))?;
|
||||
//block_on(conn.read_lines(&mut res, String::new()))?;
|
||||
//println!("out: {}", res.trim());
|
||||
block_on(conn.send_command(input.as_bytes()))?;
|
||||
block_on(conn.read_lines(&mut res, String::new()))?;
|
||||
println!("out: {}", res.trim());
|
||||
}
|
||||
Err(error) => println!("error: {}", error),
|
||||
}
|
||||
|
|
90
src/state.rs
90
src/state.rs
|
@ -134,6 +134,11 @@ impl Context {
|
|||
} = self;
|
||||
let was_online = accounts[account_pos].is_online.is_ok();
|
||||
let ret = accounts[account_pos].is_online();
|
||||
let ret = if let Some(err) = ret.as_err() {
|
||||
Err(err.clone())
|
||||
} else {
|
||||
Ok(())
|
||||
};
|
||||
if ret.is_ok() && !was_online {
|
||||
debug!("inserting mailbox hashes:");
|
||||
for mailbox_node in accounts[account_pos].list_mailboxes() {
|
||||
|
@ -163,75 +168,6 @@ impl Context {
|
|||
let idx = self.accounts.get_index_of(&account_hash).unwrap();
|
||||
self.is_online_idx(idx)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn new_mock() -> Self {
|
||||
let (sender, receiver) =
|
||||
crossbeam::channel::bounded(32 * ::std::mem::size_of::<ThreadEvent>());
|
||||
let job_executor = Arc::new(JobExecutor::new(sender.clone()));
|
||||
let input_thread = unbounded();
|
||||
let input_thread_pipe = nix::unistd::pipe()
|
||||
.map_err(|err| Box::new(err) as Box<dyn std::error::Error + Send + Sync + 'static>)
|
||||
.unwrap();
|
||||
let backends = Backends::new();
|
||||
let settings = Box::new(Settings::new().unwrap());
|
||||
let accounts = vec![{
|
||||
let name = "test".to_string();
|
||||
let mut account_conf = AccountConf::default();
|
||||
account_conf.conf.format = "maildir".to_string();
|
||||
account_conf.account.format = "maildir".to_string();
|
||||
account_conf.account.root_mailbox = "/tmp/".to_string();
|
||||
let sender = sender.clone();
|
||||
let account_hash = {
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::Hasher;
|
||||
let mut hasher = DefaultHasher::new();
|
||||
hasher.write(name.as_bytes());
|
||||
hasher.finish()
|
||||
};
|
||||
Account::new(
|
||||
account_hash,
|
||||
name,
|
||||
account_conf,
|
||||
&backends,
|
||||
job_executor.clone(),
|
||||
sender.clone(),
|
||||
BackendEventConsumer::new(Arc::new(
|
||||
move |account_hash: AccountHash, ev: BackendEvent| {
|
||||
sender
|
||||
.send(ThreadEvent::UIEvent(UIEvent::BackendEvent(
|
||||
account_hash,
|
||||
ev,
|
||||
)))
|
||||
.unwrap();
|
||||
},
|
||||
)),
|
||||
)
|
||||
.unwrap()
|
||||
}];
|
||||
let accounts = accounts.into_iter().map(|acc| (acc.hash(), acc)).collect();
|
||||
let working = Arc::new(());
|
||||
let control = Arc::downgrade(&working);
|
||||
Context {
|
||||
accounts,
|
||||
settings,
|
||||
dirty_areas: VecDeque::with_capacity(0),
|
||||
replies: VecDeque::with_capacity(0),
|
||||
temp_files: Vec::new(),
|
||||
job_executor,
|
||||
children: vec![],
|
||||
|
||||
input_thread: InputHandler {
|
||||
pipe: input_thread_pipe,
|
||||
rx: input_thread.1,
|
||||
tx: input_thread.0,
|
||||
control,
|
||||
state_tx: sender.clone(),
|
||||
},
|
||||
sender,
|
||||
receiver,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A State object to manage and own components and components of the UI. `State` is responsible for
|
||||
|
@ -1109,6 +1045,22 @@ impl State {
|
|||
self.redraw();
|
||||
return;
|
||||
}
|
||||
UIEvent::Timer(id) => {
|
||||
for i in 0..self.context.accounts.len() {
|
||||
use crate::conf::accounts::IsOnline;
|
||||
|
||||
match self.context.accounts[i].is_online {
|
||||
IsOnline::Unitialized { ref rate_limit, .. }
|
||||
| IsOnline::NoWillRetry { ref rate_limit, .. }
|
||||
if rate_limit.id() == id =>
|
||||
{
|
||||
self.context.accounts[i].retry_mailboxes();
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
UIEvent::Input(ref key)
|
||||
if *key
|
||||
== self
|
||||
|
|
31
src/types.rs
31
src/types.rs
|
@ -316,6 +316,7 @@ pub struct RateLimit {
|
|||
pub timer: crate::jobs::Timer,
|
||||
rate: std::time::Duration,
|
||||
pub active: bool,
|
||||
retries: Option<u8>,
|
||||
}
|
||||
|
||||
impl RateLimit {
|
||||
|
@ -328,9 +329,20 @@ impl RateLimit {
|
|||
),
|
||||
rate: std::time::Duration::from_millis(millis / reqs),
|
||||
active: false,
|
||||
retries: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn once(mut self) -> Self {
|
||||
self.retries = Some(1);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn exponential_backoff(mut self, n: u8) -> Self {
|
||||
self.retries = Some(n);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
self.last_tick = std::time::Instant::now();
|
||||
self.active = false;
|
||||
|
@ -341,9 +353,22 @@ impl RateLimit {
|
|||
if self.last_tick + self.rate > now {
|
||||
self.active = false;
|
||||
} else {
|
||||
self.timer.rearm();
|
||||
self.last_tick = now;
|
||||
self.active = true;
|
||||
let jitter = if self.retries.is_some() {
|
||||
Some(melib::email::compose::random::random_u64() % 1000)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some(v) = self.retries {
|
||||
self.retries = Some(v.saturating_sub(1));
|
||||
}
|
||||
self.active = self.retries.is_none() || self.retries != Some(0);
|
||||
if self.active {
|
||||
self.timer.rearm();
|
||||
self.last_tick = now;
|
||||
if let Some(jitter) = jitter {
|
||||
self.last_tick += std::time::Duration::from_millis(jitter);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.active
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue