JMAP WIP
parent
d69be5bb0b
commit
a43f6919cc
File diff suppressed because it is too large
Load Diff
|
@ -26,9 +26,11 @@ bincode = "1.2.0"
|
||||||
uuid = { version = "0.7.4", features = ["serde", "v4"] }
|
uuid = { version = "0.7.4", features = ["serde", "v4"] }
|
||||||
text_processing = { path = "../text_processing", version = "*", optional= true }
|
text_processing = { path = "../text_processing", version = "*", optional= true }
|
||||||
libc = {version = "0.2.59", features = ["extra_traits",]}
|
libc = {version = "0.2.59", features = ["extra_traits",]}
|
||||||
|
reqwest = { version ="0.10.0-alpha.2", optional=true, features = ["json", "blocking" ]}
|
||||||
|
serde_json = { version = "1.0", optional = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["unicode_algorithms", "imap_backend", "maildir_backend", "mbox_backend", "vcard"]
|
default = ["unicode_algorithms", "imap_backend", "maildir_backend", "mbox_backend", "jmap_backend", "vcard"]
|
||||||
|
|
||||||
debug-tracing = []
|
debug-tracing = []
|
||||||
unicode_algorithms = ["text_processing"]
|
unicode_algorithms = ["text_processing"]
|
||||||
|
@ -36,4 +38,5 @@ imap_backend = ["native-tls"]
|
||||||
maildir_backend = ["notify", "notify-rust", "memmap"]
|
maildir_backend = ["notify", "notify-rust", "memmap"]
|
||||||
mbox_backend = ["notify", "notify-rust", "memmap"]
|
mbox_backend = ["notify", "notify-rust", "memmap"]
|
||||||
notmuch_backend = []
|
notmuch_backend = []
|
||||||
|
jmap_backend = ["reqwest", "serde_json" ]
|
||||||
vcard = []
|
vcard = []
|
||||||
|
|
|
@ -38,6 +38,10 @@ pub mod mbox;
|
||||||
pub mod notmuch;
|
pub mod notmuch;
|
||||||
#[cfg(feature = "notmuch_backend")]
|
#[cfg(feature = "notmuch_backend")]
|
||||||
pub use self::notmuch::NotmuchDb;
|
pub use self::notmuch::NotmuchDb;
|
||||||
|
#[cfg(feature = "jmap_backend")]
|
||||||
|
pub mod jmap;
|
||||||
|
#[cfg(feature = "jmap_backend")]
|
||||||
|
pub use self::jmap::JmapType;
|
||||||
|
|
||||||
#[cfg(feature = "imap_backend")]
|
#[cfg(feature = "imap_backend")]
|
||||||
pub use self::imap::ImapType;
|
pub use self::imap::ImapType;
|
||||||
|
@ -129,6 +133,16 @@ impl Backends {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "jmap_backend")]
|
||||||
|
{
|
||||||
|
b.register(
|
||||||
|
"jmap".to_string(),
|
||||||
|
Backend {
|
||||||
|
create_fn: Box::new(|| Box::new(|f, i| JmapType::new(f, i))),
|
||||||
|
validate_conf_fn: Box::new(JmapType::validate_config),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
b
|
b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,295 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
use crate::backends::FolderHash;
|
||||||
|
use crate::backends::RefreshEvent;
|
||||||
|
use crate::backends::RefreshEventKind::{self, *};
|
||||||
|
use crate::backends::{BackendFolder, Folder, FolderOperation, MailBackend, RefreshEventConsumer};
|
||||||
|
use crate::conf::AccountSettings;
|
||||||
|
use crate::email::*;
|
||||||
|
use crate::error::{MeliError, Result};
|
||||||
|
use fnv::{FnvHashMap, FnvHashSet};
|
||||||
|
use reqwest::blocking::Client;
|
||||||
|
use std::collections::hash_map::DefaultHasher;
|
||||||
|
use std::hash::Hasher;
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::sync::{Arc, Mutex, RwLock};
|
||||||
|
|
||||||
|
pub mod protocol;
|
||||||
|
|
||||||
|
use protocol::*;
|
||||||
|
|
||||||
|
pub mod folder;
|
||||||
|
|
||||||
|
use folder::*;
|
||||||
|
|
||||||
|
#[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!(
|
||||||
|
"Configuration error ({}): IMAP 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))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct JmapType {
|
||||||
|
account_name: String,
|
||||||
|
online: Arc<Mutex<bool>>,
|
||||||
|
is_subscribed: Arc<IsSubscribedFn>,
|
||||||
|
server_conf: JmapServerConf,
|
||||||
|
connection: Arc<Mutex<JmapConnection>>,
|
||||||
|
folders: Arc<RwLock<FnvHashMap<FolderHash, JmapFolder>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MailBackend for JmapType {
|
||||||
|
fn is_online(&self) -> bool {
|
||||||
|
*self.online.lock().unwrap()
|
||||||
|
}
|
||||||
|
fn get(&mut self, folder: &Folder) -> Async<Result<Vec<Envelope>>> {
|
||||||
|
let mut w = AsyncBuilder::new();
|
||||||
|
let folders = self.folders.clone();
|
||||||
|
let connection = self.connection.clone();
|
||||||
|
let folder_hash = folder.hash();
|
||||||
|
let handle = {
|
||||||
|
let tx = w.tx();
|
||||||
|
let closure = move |_work_context| {
|
||||||
|
let mut conn_lck = connection.lock().unwrap();
|
||||||
|
tx.send(AsyncStatus::Payload(
|
||||||
|
protocol::get_message_list(
|
||||||
|
&mut conn_lck,
|
||||||
|
&folders.read().unwrap()[&folder_hash],
|
||||||
|
)
|
||||||
|
.and_then(|ids| {
|
||||||
|
protocol::get_message(&mut conn_lck, std::dbg!(&ids).as_slice())
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
|
tx.send(AsyncStatus::Finished).unwrap();
|
||||||
|
};
|
||||||
|
Box::new(closure)
|
||||||
|
};
|
||||||
|
w.build(handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn watch(
|
||||||
|
&self,
|
||||||
|
sender: RefreshEventConsumer,
|
||||||
|
work_context: WorkContext,
|
||||||
|
) -> Result<std::thread::ThreadId> {
|
||||||
|
Err(MeliError::from("sadfsa"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn folders(&self) -> Result<FnvHashMap<FolderHash, Folder>> {
|
||||||
|
if self.folders.read().unwrap().is_empty() {
|
||||||
|
let folders = std::dbg!(protocol::get_mailboxes(
|
||||||
|
&mut self.connection.lock().unwrap()
|
||||||
|
))?;
|
||||||
|
let ret = Ok(folders
|
||||||
|
.iter()
|
||||||
|
.map(|(&h, f)| (h, BackendFolder::clone(f) as Folder))
|
||||||
|
.collect());
|
||||||
|
*self.folders.write().unwrap() = folders;
|
||||||
|
ret
|
||||||
|
} else {
|
||||||
|
Ok(self
|
||||||
|
.folders
|
||||||
|
.read()
|
||||||
|
.unwrap()
|
||||||
|
.iter()
|
||||||
|
.map(|(&h, f)| (h, BackendFolder::clone(f) as Folder))
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn operation(&self, hash: EnvelopeHash) -> Box<dyn BackendOp> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save(&self, bytes: &[u8], folder: &str, flags: Option<Flag>) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn folder_operation(&mut self, path: &str, op: FolderOperation) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_any(&self) -> &dyn::std::any::Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JmapType {
|
||||||
|
pub fn new(
|
||||||
|
s: &AccountSettings,
|
||||||
|
is_subscribed: Box<dyn Fn(&str) -> bool + Send + Sync>,
|
||||||
|
) -> Result<Box<dyn MailBackend>> {
|
||||||
|
let online = Arc::new(Mutex::new(false));
|
||||||
|
let server_conf = JmapServerConf::new(s)?;
|
||||||
|
|
||||||
|
Ok(Box::new(JmapType {
|
||||||
|
connection: Arc::new(Mutex::new(JmapConnection::new(
|
||||||
|
&server_conf,
|
||||||
|
online.clone(),
|
||||||
|
)?)),
|
||||||
|
folders: Arc::new(RwLock::new(FnvHashMap::default())),
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct JmapConnection {
|
||||||
|
request_no: usize,
|
||||||
|
client: Client,
|
||||||
|
online_status: Arc<Mutex<bool>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JmapConnection {
|
||||||
|
pub fn new(server_conf: &JmapServerConf, online_status: Arc<Mutex<bool>>) -> Result<Self> {
|
||||||
|
use reqwest::header;
|
||||||
|
let mut headers = header::HeaderMap::new();
|
||||||
|
headers.insert(
|
||||||
|
header::AUTHORIZATION,
|
||||||
|
header::HeaderValue::from_static("fc32dffe-14e7-11ea-a277-2477037a1804"),
|
||||||
|
);
|
||||||
|
headers.insert(
|
||||||
|
header::ACCEPT,
|
||||||
|
header::HeaderValue::from_static("application/json"),
|
||||||
|
);
|
||||||
|
headers.insert(
|
||||||
|
header::CONTENT_TYPE,
|
||||||
|
header::HeaderValue::from_static("application/json"),
|
||||||
|
);
|
||||||
|
Ok(JmapConnection {
|
||||||
|
request_no: 0,
|
||||||
|
client: reqwest::blocking::ClientBuilder::new()
|
||||||
|
.danger_accept_invalid_certs(server_conf.danger_accept_invalid_certs)
|
||||||
|
.default_headers(headers)
|
||||||
|
.build()?,
|
||||||
|
online_status,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
/*
|
||||||
|
* 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 super::*;
|
||||||
|
use crate::backends::{FolderPermissions, SpecialUsageMailbox};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct JmapFolder {
|
||||||
|
pub name: String,
|
||||||
|
pub path: String,
|
||||||
|
pub hash: FolderHash,
|
||||||
|
pub v: Vec<FolderHash>,
|
||||||
|
pub id: String,
|
||||||
|
pub is_subscribed: bool,
|
||||||
|
pub my_rights: JmapRights,
|
||||||
|
pub parent_id: Option<String>,
|
||||||
|
pub role: Option<String>,
|
||||||
|
pub sort_order: u64,
|
||||||
|
pub total_emails: u64,
|
||||||
|
pub total_threads: u64,
|
||||||
|
pub unread_emails: u64,
|
||||||
|
pub unread_threads: u64,
|
||||||
|
pub usage: SpecialUsageMailbox,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BackendFolder for JmapFolder {
|
||||||
|
fn hash(&self) -> FolderHash {
|
||||||
|
self.hash
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
fn path(&self) -> &str {
|
||||||
|
&self.path
|
||||||
|
}
|
||||||
|
|
||||||
|
fn change_name(&mut self, _s: &str) {}
|
||||||
|
|
||||||
|
fn clone(&self) -> Folder {
|
||||||
|
Box::new(std::clone::Clone::clone(self))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn children(&self) -> &[FolderHash] {
|
||||||
|
&self.v
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parent(&self) -> Option<FolderHash> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn permissions(&self) -> FolderPermissions {
|
||||||
|
FolderPermissions::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn special_usage(&self) -> SpecialUsageMailbox {
|
||||||
|
self.usage
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,261 @@
|
||||||
|
/*
|
||||||
|
* 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 super::folder::JmapFolder;
|
||||||
|
use super::*;
|
||||||
|
use serde_json::{json, Value};
|
||||||
|
|
||||||
|
macro_rules! get_path_hash {
|
||||||
|
($path:expr) => {{
|
||||||
|
use std::collections::hash_map::DefaultHasher;
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
|
let mut hasher = DefaultHasher::new();
|
||||||
|
$path.hash(&mut hasher);
|
||||||
|
hasher.finish()
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
static USING: &'static [&'static str] = &["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"];
|
||||||
|
|
||||||
|
pub fn get_mailboxes(conn: &mut JmapConnection) -> Result<FnvHashMap<FolderHash, JmapFolder>> {
|
||||||
|
let res = conn
|
||||||
|
.client
|
||||||
|
.post("https://jmap-proxy.local/jmap/fc32dffe-14e7-11ea-a277-2477037a1804/")
|
||||||
|
.json(&json!({
|
||||||
|
"using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
|
||||||
|
"methodCalls": [["Mailbox/get", {},
|
||||||
|
format!("#m{}", conn.request_no + 1).as_str()]],
|
||||||
|
}))
|
||||||
|
.send();
|
||||||
|
conn.request_no += 1;
|
||||||
|
|
||||||
|
let mut v: JsonResponse =
|
||||||
|
serde_json::from_str(&std::dbg!(res.unwrap().text().unwrap())).unwrap();
|
||||||
|
*conn.online_status.lock().unwrap() = true;
|
||||||
|
std::dbg!(&v);
|
||||||
|
assert_eq!("Mailbox/get", v.method_responses[0].0);
|
||||||
|
Ok(
|
||||||
|
if let Response::MailboxGet { list, .. } = v.method_responses.remove(0).1 {
|
||||||
|
list.into_iter().map(|r| {
|
||||||
|
if let MailboxResponse {
|
||||||
|
id,
|
||||||
|
is_subscribed,
|
||||||
|
my_rights,
|
||||||
|
name,
|
||||||
|
parent_id,
|
||||||
|
role,
|
||||||
|
sort_order,
|
||||||
|
total_emails,
|
||||||
|
total_threads,
|
||||||
|
unread_emails,
|
||||||
|
unread_threads,
|
||||||
|
} = r
|
||||||
|
{
|
||||||
|
let hash = get_path_hash!(&name);
|
||||||
|
(
|
||||||
|
hash,
|
||||||
|
JmapFolder {
|
||||||
|
name: name.clone(),
|
||||||
|
hash,
|
||||||
|
path: name,
|
||||||
|
v: Vec::new(),
|
||||||
|
id,
|
||||||
|
is_subscribed,
|
||||||
|
my_rights,
|
||||||
|
parent_id,
|
||||||
|
role,
|
||||||
|
usage: Default::default(),
|
||||||
|
sort_order,
|
||||||
|
total_emails,
|
||||||
|
total_threads,
|
||||||
|
unread_emails,
|
||||||
|
unread_threads,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
panic!()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
panic!()
|
||||||
|
}
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct JsonResponse {
|
||||||
|
method_responses: Vec<MethodResponse>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct MethodResponse(String, Response, String);
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum Response {
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
MailboxGet {
|
||||||
|
account_id: String,
|
||||||
|
list: Vec<MailboxResponse>,
|
||||||
|
not_found: Vec<String>,
|
||||||
|
state: String,
|
||||||
|
},
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
EmailQuery {
|
||||||
|
account_id: String,
|
||||||
|
can_calculate_changes: bool,
|
||||||
|
collapse_threads: bool,
|
||||||
|
filter: Value,
|
||||||
|
ids: Vec<String>,
|
||||||
|
position: u64,
|
||||||
|
query_state: String,
|
||||||
|
sort: Option<String>,
|
||||||
|
total: usize,
|
||||||
|
},
|
||||||
|
Empty {},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct MailboxResponse {
|
||||||
|
id: String,
|
||||||
|
is_subscribed: bool,
|
||||||
|
my_rights: JmapRights,
|
||||||
|
name: String,
|
||||||
|
parent_id: Option<String>,
|
||||||
|
role: Option<String>,
|
||||||
|
sort_order: u64,
|
||||||
|
total_emails: u64,
|
||||||
|
total_threads: u64,
|
||||||
|
unread_emails: u64,
|
||||||
|
unread_threads: u64,
|
||||||
|
}
|
||||||
|
#[derive(Deserialize, Debug, Clone)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct JmapRights {
|
||||||
|
may_add_items: bool,
|
||||||
|
may_create_child: bool,
|
||||||
|
may_delete: bool,
|
||||||
|
may_read_items: bool,
|
||||||
|
may_remove_items: bool,
|
||||||
|
may_rename: bool,
|
||||||
|
may_set_keywords: bool,
|
||||||
|
may_set_seen: bool,
|
||||||
|
may_submit: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
// [
|
||||||
|
// [ "getMessageList", {
|
||||||
|
// filter: {
|
||||||
|
// inMailboxes: [ "mailbox1" ]
|
||||||
|
// },
|
||||||
|
// sort: [ "date desc", "id desc" ]
|
||||||
|
// collapseThreads: true,
|
||||||
|
// position: 0,
|
||||||
|
// limit: 10,
|
||||||
|
// fetchThreads: true,
|
||||||
|
// fetchMessages: true,
|
||||||
|
// fetchMessageProperties: [
|
||||||
|
// "threadId",
|
||||||
|
// "mailboxId",
|
||||||
|
// "isUnread",
|
||||||
|
// "isFlagged",
|
||||||
|
// "isAnswered",
|
||||||
|
// "isDraft",
|
||||||
|
// "hasAttachment",
|
||||||
|
// "from",
|
||||||
|
// "to",
|
||||||
|
// "subject",
|
||||||
|
// "date",
|
||||||
|
// "preview"
|
||||||
|
// ],
|
||||||
|
// fetchSearchSnippets: false
|
||||||
|
// }, "call1"]
|
||||||
|
// ]
|
||||||
|
pub fn get_message_list(conn: &mut JmapConnection, folder: &JmapFolder) -> Result<Vec<String>> {
|
||||||
|
let res = conn
|
||||||
|
.client
|
||||||
|
.post("https://jmap-proxy.local/jmap/fc32dffe-14e7-11ea-a277-2477037a1804/")
|
||||||
|
.json(&json!({
|
||||||
|
"using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
|
||||||
|
"methodCalls": [["Email/query", { "filter": {
|
||||||
|
"inMailboxes": [ folder.id ]
|
||||||
|
},
|
||||||
|
"collapseThreads": false,
|
||||||
|
"position": 0,
|
||||||
|
"fetchThreads": true,
|
||||||
|
"fetchMessages": true,
|
||||||
|
"fetchMessageProperties": [
|
||||||
|
"threadId",
|
||||||
|
"mailboxId",
|
||||||
|
"isUnread",
|
||||||
|
"isFlagged",
|
||||||
|
"isAnswered",
|
||||||
|
"isDraft",
|
||||||
|
"hasAttachment",
|
||||||
|
"from",
|
||||||
|
"to",
|
||||||
|
"subject",
|
||||||
|
"date",
|
||||||
|
"preview"
|
||||||
|
],
|
||||||
|
}, format!("#m{}", conn.request_no + 1).as_str()]],
|
||||||
|
}))
|
||||||
|
.send();
|
||||||
|
|
||||||
|
conn.request_no += 1;
|
||||||
|
let mut v: JsonResponse = serde_json::from_str(&std::dbg!(res.unwrap().text().unwrap()))?;
|
||||||
|
|
||||||
|
let result: Response = v.method_responses.remove(0).1;
|
||||||
|
if let Response::EmailQuery { ids, .. } = result {
|
||||||
|
Ok(ids)
|
||||||
|
} else {
|
||||||
|
Err(MeliError::new(format!("response was {:#?}", &result)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_message(conn: &mut JmapConnection, ids: &[String]) -> Result<Vec<Envelope>> {
|
||||||
|
let res = conn
|
||||||
|
.client
|
||||||
|
.post("https://jmap-proxy.local/jmap/fc32dffe-14e7-11ea-a277-2477037a1804/")
|
||||||
|
.json(&json!({
|
||||||
|
"using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
|
||||||
|
"methodCalls": [["Email/get", {
|
||||||
|
"ids": ids,
|
||||||
|
"properties": [ "threadId", "mailboxIds", "from", "subject",
|
||||||
|
"receivedAt",
|
||||||
|
"htmlBody", "bodyValues" ],
|
||||||
|
"bodyProperties": [ "partId", "blobId", "size", "type" ],
|
||||||
|
"fetchHTMLBodyValues": true,
|
||||||
|
"maxBodyValueBytes": 256
|
||||||
|
}, format!("#m{}", conn.request_no + 1).as_str()]],
|
||||||
|
}))
|
||||||
|
.send();
|
||||||
|
conn.request_no += 1;
|
||||||
|
|
||||||
|
let v: JsonResponse = serde_json::from_str(&std::dbg!(res.unwrap().text().unwrap()))?;
|
||||||
|
std::dbg!(&v);
|
||||||
|
Ok(vec![])
|
||||||
|
}
|
|
@ -151,6 +151,22 @@ impl From<std::num::ParseIntError> for MeliError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "jmap_backend")]
|
||||||
|
impl From<reqwest::Error> for MeliError {
|
||||||
|
#[inline]
|
||||||
|
fn from(kind: reqwest::Error) -> MeliError {
|
||||||
|
MeliError::new(format!("{}", kind))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "jmap_backend")]
|
||||||
|
impl From<serde_json::error::Error> for MeliError {
|
||||||
|
#[inline]
|
||||||
|
fn from(kind: serde_json::error::Error) -> MeliError {
|
||||||
|
MeliError::new(format!("{}", kind))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<&str> for MeliError {
|
impl From<&str> for MeliError {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn from(kind: &str) -> MeliError {
|
fn from(kind: &str) -> MeliError {
|
||||||
|
|
Loading…
Reference in New Issue