2019-12-03 11:25:49 +00:00
|
|
|
/*
|
|
|
|
* meli - jmap module.
|
|
|
|
*
|
|
|
|
* Copyright 2019 Manos Pitsidianakis
|
|
|
|
*
|
|
|
|
* This file is part of meli.
|
|
|
|
*
|
|
|
|
* meli is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* meli is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
use crate::async_workers::{Async, AsyncBuilder, AsyncStatus, WorkContext};
|
|
|
|
use crate::backends::BackendOp;
|
2020-02-26 08:54:10 +00:00
|
|
|
use crate::backends::MailboxHash;
|
|
|
|
use crate::backends::{BackendMailbox, MailBackend, Mailbox, RefreshEventConsumer};
|
2019-12-03 11:25:49 +00:00
|
|
|
use crate::conf::AccountSettings;
|
|
|
|
use crate::email::*;
|
|
|
|
use crate::error::{MeliError, Result};
|
2019-12-07 12:03:54 +00:00
|
|
|
use fnv::FnvHashMap;
|
2019-12-03 11:25:49 +00:00
|
|
|
use reqwest::blocking::Client;
|
2019-12-07 14:44:29 +00:00
|
|
|
use std::collections::BTreeMap;
|
2019-12-03 11:25:49 +00:00
|
|
|
use std::str::FromStr;
|
|
|
|
use std::sync::{Arc, Mutex, RwLock};
|
2019-12-14 16:46:12 +00:00
|
|
|
use std::time::Instant;
|
2019-12-03 11:25:49 +00:00
|
|
|
|
2019-12-03 23:04:38 +00:00
|
|
|
#[macro_export]
|
|
|
|
macro_rules! _impl {
|
2019-12-06 12:12:27 +00:00
|
|
|
($(#[$outer:meta])*$field:ident : $t:ty) => {
|
|
|
|
$(#[$outer])*
|
2019-12-03 23:04:38 +00:00
|
|
|
pub fn $field(mut self, new_val: $t) -> Self {
|
|
|
|
self.$field = new_val;
|
|
|
|
self
|
|
|
|
}
|
2019-12-06 12:12:27 +00:00
|
|
|
};
|
|
|
|
(get_mut $(#[$outer:meta])*$method:ident, $field:ident : $t:ty) => {
|
|
|
|
$(#[$outer])*
|
2019-12-04 17:42:31 +00:00
|
|
|
pub fn $method(&mut self) -> &mut $t {
|
|
|
|
&mut self.$field
|
|
|
|
}
|
2019-12-06 12:12:27 +00:00
|
|
|
};
|
|
|
|
(get $(#[$outer:meta])*$method:ident, $field:ident : $t:ty) => {
|
|
|
|
$(#[$outer])*
|
|
|
|
pub fn $method(&self) -> &$t {
|
|
|
|
&self.$field
|
|
|
|
}
|
2019-12-04 17:42:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-06 08:06:15 +00:00
|
|
|
pub mod operations;
|
|
|
|
use operations::*;
|
|
|
|
|
2019-12-03 23:04:38 +00:00
|
|
|
pub mod connection;
|
|
|
|
use connection::*;
|
|
|
|
|
2019-12-03 11:25:49 +00:00
|
|
|
pub mod protocol;
|
|
|
|
use protocol::*;
|
|
|
|
|
2019-12-03 19:29:26 +00:00
|
|
|
pub mod rfc8620;
|
|
|
|
use rfc8620::*;
|
2019-12-03 11:25:49 +00:00
|
|
|
|
2019-12-03 19:29:26 +00:00
|
|
|
pub mod objects;
|
|
|
|
use objects::*;
|
|
|
|
|
2020-02-26 08:54:10 +00:00
|
|
|
pub mod mailbox;
|
|
|
|
use mailbox::*;
|
2019-12-03 11:25:49 +00:00
|
|
|
|
|
|
|
#[derive(Debug, Default)]
|
|
|
|
pub struct EnvelopeCache {
|
|
|
|
bytes: Option<String>,
|
|
|
|
headers: Option<String>,
|
|
|
|
body: Option<String>,
|
|
|
|
flags: Option<Flag>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
pub struct JmapServerConf {
|
|
|
|
pub server_hostname: String,
|
|
|
|
pub server_username: String,
|
|
|
|
pub server_password: String,
|
|
|
|
pub server_port: u16,
|
|
|
|
pub danger_accept_invalid_certs: bool,
|
|
|
|
}
|
|
|
|
|
|
|
|
macro_rules! get_conf_val {
|
|
|
|
($s:ident[$var:literal]) => {
|
|
|
|
$s.extra.get($var).ok_or_else(|| {
|
|
|
|
MeliError::new(format!(
|
|
|
|
"Configuration error ({}): JMAP connection requires the field `{}` set",
|
|
|
|
$s.name.as_str(),
|
|
|
|
$var
|
|
|
|
))
|
|
|
|
})
|
|
|
|
};
|
|
|
|
($s:ident[$var:literal], $default:expr) => {
|
|
|
|
$s.extra
|
|
|
|
.get($var)
|
|
|
|
.map(|v| {
|
|
|
|
<_>::from_str(v).map_err(|e| {
|
|
|
|
MeliError::new(format!(
|
|
|
|
"Configuration error ({}): Invalid value for field `{}`: {}\n{}",
|
|
|
|
$s.name.as_str(),
|
|
|
|
$var,
|
|
|
|
v,
|
|
|
|
e
|
|
|
|
))
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.unwrap_or_else(|| Ok($default))
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
impl JmapServerConf {
|
|
|
|
pub fn new(s: &AccountSettings) -> Result<Self> {
|
|
|
|
Ok(JmapServerConf {
|
|
|
|
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)?,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct IsSubscribedFn(Box<dyn Fn(&str) -> bool + Send + Sync>);
|
|
|
|
|
|
|
|
impl std::fmt::Debug for IsSubscribedFn {
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
|
|
write!(f, "IsSubscribedFn Box")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl std::ops::Deref for IsSubscribedFn {
|
|
|
|
type Target = Box<dyn Fn(&str) -> bool + Send + Sync>;
|
|
|
|
fn deref(&self) -> &Box<dyn Fn(&str) -> bool + Send + Sync> {
|
|
|
|
&self.0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
macro_rules! get_conf_val {
|
|
|
|
($s:ident[$var:literal]) => {
|
|
|
|
$s.extra.get($var).ok_or_else(|| {
|
|
|
|
MeliError::new(format!(
|
2019-12-03 23:04:38 +00:00
|
|
|
"Configuration error ({}): JMAP connection requires the field `{}` set",
|
2019-12-03 11:25:49 +00:00
|
|
|
$s.name.as_str(),
|
|
|
|
$var
|
|
|
|
))
|
|
|
|
})
|
|
|
|
};
|
|
|
|
($s:ident[$var:literal], $default:expr) => {
|
|
|
|
$s.extra
|
|
|
|
.get($var)
|
|
|
|
.map(|v| {
|
|
|
|
<_>::from_str(v).map_err(|e| {
|
|
|
|
MeliError::new(format!(
|
|
|
|
"Configuration error ({}): Invalid value for field `{}`: {}\n{}",
|
|
|
|
$s.name.as_str(),
|
|
|
|
$var,
|
|
|
|
v,
|
|
|
|
e
|
|
|
|
))
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.unwrap_or_else(|| Ok($default))
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2019-12-06 08:06:15 +00:00
|
|
|
#[derive(Debug, Default)]
|
|
|
|
pub struct Store {
|
|
|
|
byte_cache: FnvHashMap<EnvelopeHash, EnvelopeCache>,
|
|
|
|
id_store: FnvHashMap<EnvelopeHash, Id>,
|
|
|
|
blob_id_store: FnvHashMap<EnvelopeHash, Id>,
|
|
|
|
}
|
|
|
|
|
2019-12-03 11:25:49 +00:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct JmapType {
|
|
|
|
account_name: String,
|
2019-12-14 16:46:12 +00:00
|
|
|
online: Arc<Mutex<(Instant, Result<()>)>>,
|
2019-12-03 11:25:49 +00:00
|
|
|
is_subscribed: Arc<IsSubscribedFn>,
|
|
|
|
server_conf: JmapServerConf,
|
2019-12-03 23:04:38 +00:00
|
|
|
connection: Arc<JmapConnection>,
|
2019-12-06 08:06:15 +00:00
|
|
|
store: Arc<RwLock<Store>>,
|
2019-12-07 14:44:29 +00:00
|
|
|
tag_index: Arc<RwLock<BTreeMap<u64, String>>>,
|
2020-02-26 08:54:10 +00:00
|
|
|
mailboxes: Arc<RwLock<FnvHashMap<MailboxHash, JmapMailbox>>>,
|
2019-12-03 11:25:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl MailBackend for JmapType {
|
2019-12-14 16:46:12 +00:00
|
|
|
fn is_online(&self) -> Result<()> {
|
|
|
|
self.online.lock().unwrap().1.clone()
|
2019-12-03 11:25:49 +00:00
|
|
|
}
|
2019-12-14 16:55:08 +00:00
|
|
|
|
|
|
|
fn connect(&mut self) {
|
|
|
|
if self.is_online().is_err() {
|
|
|
|
if Instant::now().duration_since(self.online.lock().unwrap().0)
|
|
|
|
>= std::time::Duration::new(2, 0)
|
|
|
|
{
|
2020-02-26 08:54:10 +00:00
|
|
|
let _ = self.mailboxes();
|
2019-12-14 16:55:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-26 08:54:10 +00:00
|
|
|
fn get(&mut self, mailbox: &Mailbox) -> Async<Result<Vec<Envelope>>> {
|
2019-12-03 11:25:49 +00:00
|
|
|
let mut w = AsyncBuilder::new();
|
2020-02-26 08:54:10 +00:00
|
|
|
let mailboxes = self.mailboxes.clone();
|
2019-12-06 08:06:15 +00:00
|
|
|
let store = self.store.clone();
|
2019-12-07 14:44:29 +00:00
|
|
|
let tag_index = self.tag_index.clone();
|
2019-12-03 11:25:49 +00:00
|
|
|
let connection = self.connection.clone();
|
2020-02-26 08:54:10 +00:00
|
|
|
let mailbox_hash = mailbox.hash();
|
2019-12-03 11:25:49 +00:00
|
|
|
let handle = {
|
|
|
|
let tx = w.tx();
|
|
|
|
let closure = move |_work_context| {
|
2019-12-04 22:04:03 +00:00
|
|
|
tx.send(AsyncStatus::Payload(protocol::get(
|
|
|
|
&connection,
|
2019-12-06 08:06:15 +00:00
|
|
|
&store,
|
2019-12-07 14:44:29 +00:00
|
|
|
&tag_index,
|
2020-02-26 08:54:10 +00:00
|
|
|
&mailboxes.read().unwrap()[&mailbox_hash],
|
2019-12-04 22:04:03 +00:00
|
|
|
)))
|
2019-12-03 11:25:49 +00:00
|
|
|
.unwrap();
|
|
|
|
tx.send(AsyncStatus::Finished).unwrap();
|
|
|
|
};
|
|
|
|
Box::new(closure)
|
|
|
|
};
|
|
|
|
w.build(handle)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn watch(
|
|
|
|
&self,
|
2019-12-07 12:03:54 +00:00
|
|
|
_sender: RefreshEventConsumer,
|
|
|
|
_work_context: WorkContext,
|
2019-12-03 11:25:49 +00:00
|
|
|
) -> Result<std::thread::ThreadId> {
|
2020-03-28 09:43:32 +00:00
|
|
|
Err(MeliError::from("JMAP watch for updates is unimplemented"))
|
2019-12-03 11:25:49 +00:00
|
|
|
}
|
|
|
|
|
2020-02-26 08:54:10 +00:00
|
|
|
fn mailboxes(&self) -> Result<FnvHashMap<MailboxHash, Mailbox>> {
|
|
|
|
if self.mailboxes.read().unwrap().is_empty() {
|
|
|
|
let mailboxes = std::dbg!(protocol::get_mailboxes(&self.connection))?;
|
|
|
|
*self.mailboxes.write().unwrap() = mailboxes;
|
2019-12-03 11:25:49 +00:00
|
|
|
}
|
2019-12-18 13:44:44 +00:00
|
|
|
|
|
|
|
Ok(self
|
2020-02-26 08:54:10 +00:00
|
|
|
.mailboxes
|
2019-12-18 13:44:44 +00:00
|
|
|
.read()
|
|
|
|
.unwrap()
|
|
|
|
.iter()
|
|
|
|
.filter(|(_, f)| f.is_subscribed)
|
2020-02-26 08:54:10 +00:00
|
|
|
.map(|(&h, f)| (h, BackendMailbox::clone(f) as Mailbox))
|
2019-12-18 13:44:44 +00:00
|
|
|
.collect())
|
2019-12-03 11:25:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn operation(&self, hash: EnvelopeHash) -> Box<dyn BackendOp> {
|
2019-12-06 08:06:15 +00:00
|
|
|
Box::new(JmapOp::new(
|
|
|
|
hash,
|
|
|
|
self.connection.clone(),
|
|
|
|
self.store.clone(),
|
|
|
|
))
|
2019-12-03 11:25:49 +00:00
|
|
|
}
|
|
|
|
|
2020-02-26 08:54:10 +00:00
|
|
|
fn save(&self, _bytes: &[u8], _mailbox: &str, _flags: Option<Flag>) -> Result<()> {
|
2019-12-03 11:25:49 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn as_any(&self) -> &dyn::std::any::Any {
|
|
|
|
self
|
|
|
|
}
|
2019-12-07 14:44:29 +00:00
|
|
|
|
|
|
|
fn tags(&self) -> Option<Arc<RwLock<BTreeMap<u64, String>>>> {
|
|
|
|
Some(self.tag_index.clone())
|
|
|
|
}
|
2019-12-03 11:25:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl JmapType {
|
|
|
|
pub fn new(
|
|
|
|
s: &AccountSettings,
|
|
|
|
is_subscribed: Box<dyn Fn(&str) -> bool + Send + Sync>,
|
|
|
|
) -> Result<Box<dyn MailBackend>> {
|
2019-12-17 12:12:41 +00:00
|
|
|
let online = Arc::new(Mutex::new((
|
|
|
|
std::time::Instant::now(),
|
|
|
|
Err(MeliError::new("Account is uninitialised.")),
|
|
|
|
)));
|
2019-12-03 11:25:49 +00:00
|
|
|
let server_conf = JmapServerConf::new(s)?;
|
|
|
|
|
|
|
|
Ok(Box::new(JmapType {
|
2019-12-03 23:04:38 +00:00
|
|
|
connection: Arc::new(JmapConnection::new(&server_conf, online.clone())?),
|
2019-12-06 08:06:15 +00:00
|
|
|
store: Arc::new(RwLock::new(Store::default())),
|
2019-12-07 14:44:29 +00:00
|
|
|
tag_index: Arc::new(RwLock::new(Default::default())),
|
2020-02-26 08:54:10 +00:00
|
|
|
mailboxes: Arc::new(RwLock::new(FnvHashMap::default())),
|
2019-12-03 11:25:49 +00:00
|
|
|
account_name: s.name.clone(),
|
|
|
|
online,
|
|
|
|
is_subscribed: Arc::new(IsSubscribedFn(is_subscribed)),
|
|
|
|
server_conf,
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn validate_config(s: &AccountSettings) -> Result<()> {
|
|
|
|
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(())
|
|
|
|
}
|
|
|
|
}
|