mirror of
https://github.com/pimalaya/himalaya.git
synced 2026-06-17 13:17:55 +08:00
use std clients
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
use anyhow::Result;
|
||||
use clap::Subcommand;
|
||||
use pimalaya_toolbox::terminal::printer::Printer;
|
||||
use pimalaya_cli::printer::Printer;
|
||||
|
||||
use crate::jmap::{
|
||||
account::JmapAccount,
|
||||
+7
-37
@@ -1,20 +1,12 @@
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
io::{Read, Write},
|
||||
};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use clap::Parser;
|
||||
use io_jmap::rfc8621::{
|
||||
email::EmailCopy,
|
||||
email_copy::{JmapEmailCopy, JmapEmailCopyResult},
|
||||
};
|
||||
use pimalaya_toolbox::terminal::printer::{Message, Printer};
|
||||
use io_jmap::rfc8621::email::EmailCopy;
|
||||
use pimalaya_cli::printer::{Message, Printer};
|
||||
|
||||
use crate::jmap::{account::JmapAccount, error::format_set_error};
|
||||
|
||||
const READ_BUFFER_SIZE: usize = 16 * 1024;
|
||||
|
||||
/// Copy JMAP emails from another account (Email/copy).
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct JmapEmailCopyCommand {
|
||||
@@ -33,7 +25,7 @@ pub struct JmapEmailCopyCommand {
|
||||
|
||||
impl JmapEmailCopyCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, account: JmapAccount) -> Result<()> {
|
||||
let mut jmap = account.new_jmap_session()?;
|
||||
let mut client = account.new_jmap_client()?;
|
||||
|
||||
let mailbox_ids: BTreeMap<String, bool> =
|
||||
self.mailbox_id.into_iter().map(|m| (m, true)).collect();
|
||||
@@ -54,34 +46,12 @@ impl JmapEmailCopyCommand {
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut coroutine = JmapEmailCopy::new(
|
||||
&jmap.session,
|
||||
&jmap.http_auth,
|
||||
self.from_account.clone(),
|
||||
emails,
|
||||
)?;
|
||||
let mut buf = [0u8; READ_BUFFER_SIZE];
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
let output = client.email_copy(self.from_account.clone(), emails)?;
|
||||
|
||||
let not_created = loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
JmapEmailCopyResult::Ok { not_created, .. } => break not_created,
|
||||
JmapEmailCopyResult::WantsRead => {
|
||||
let n = jmap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
JmapEmailCopyResult::WantsWrite(bytes) => {
|
||||
jmap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
JmapEmailCopyResult::Err(err) => bail!("{err}"),
|
||||
}
|
||||
};
|
||||
|
||||
if !not_created.is_empty() {
|
||||
if !output.not_created.is_empty() {
|
||||
let mut msg = String::from("Copy JMAP email(s) error");
|
||||
|
||||
for (id, err) in not_created {
|
||||
for (id, err) in output.not_created {
|
||||
msg.push_str(&format!("\n `{id}`"));
|
||||
msg.push_str(&format_set_error(&err));
|
||||
}
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
use std::io::{Read, Write};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use clap::Parser;
|
||||
use io_jmap::rfc8621::email_set::{JmapEmailSet, JmapEmailSetArgs, JmapEmailSetResult};
|
||||
use pimalaya_toolbox::terminal::printer::{Message, Printer};
|
||||
use io_jmap::rfc8621::email_set::JmapEmailSetArgs;
|
||||
use pimalaya_cli::printer::{Message, Printer};
|
||||
|
||||
use crate::jmap::{account::JmapAccount, error::format_set_error};
|
||||
|
||||
const READ_BUFFER_SIZE: usize = 16 * 1024;
|
||||
|
||||
/// Delete JMAP emails (Email/set destroy).
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct JmapEmailDestroyCommand {
|
||||
@@ -19,37 +15,19 @@ pub struct JmapEmailDestroyCommand {
|
||||
|
||||
impl JmapEmailDestroyCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, account: JmapAccount) -> Result<()> {
|
||||
let mut jmap = account.new_jmap_session()?;
|
||||
|
||||
let mut client = account.new_jmap_client()?;
|
||||
let mut args = JmapEmailSetArgs::default();
|
||||
|
||||
for id in self.ids {
|
||||
args.destroy(id);
|
||||
}
|
||||
|
||||
let mut coroutine = JmapEmailSet::new(&jmap.session, &jmap.http_auth, args)?;
|
||||
let mut buf = [0u8; READ_BUFFER_SIZE];
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
let output = client.email_set(args)?;
|
||||
|
||||
let not_destroyed = loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
JmapEmailSetResult::Ok { not_destroyed, .. } => break not_destroyed,
|
||||
JmapEmailSetResult::WantsRead => {
|
||||
let n = jmap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
JmapEmailSetResult::WantsWrite(bytes) => {
|
||||
jmap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
JmapEmailSetResult::Err(err) => bail!("{err}"),
|
||||
}
|
||||
};
|
||||
|
||||
if !not_destroyed.is_empty() {
|
||||
if !output.not_destroyed.is_empty() {
|
||||
let mut msg = String::from("Destroy JMAP email(s) error");
|
||||
|
||||
for (id, err) in not_destroyed {
|
||||
for (id, err) in output.not_destroyed {
|
||||
msg.push_str(&format!("\n `{id}`"));
|
||||
msg.push_str(&format_set_error(&err));
|
||||
}
|
||||
|
||||
+32
-71
@@ -1,20 +1,14 @@
|
||||
use std::io::{Read, Write};
|
||||
use std::net::TcpStream;
|
||||
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use anyhow::{anyhow, Result};
|
||||
use clap::Parser;
|
||||
use io_jmap::{
|
||||
rfc8620::blob_download::{JmapBlobDownload, JmapBlobDownloadResult},
|
||||
rfc8621::{
|
||||
capabilities::MAIL,
|
||||
email_get::{JmapEmailGet, JmapEmailGetResult},
|
||||
},
|
||||
};
|
||||
use pimalaya_toolbox::terminal::printer::{Message, Printer};
|
||||
use io_jmap::{client::JmapClient, rfc8621::capabilities::MAIL};
|
||||
use pimalaya_cli::printer::{Message, Printer};
|
||||
use pimalaya_stream::tls::upgrade_tls;
|
||||
use secrecy::SecretString;
|
||||
use url::Url;
|
||||
|
||||
use crate::jmap::account::JmapAccount;
|
||||
|
||||
const READ_BUFFER_SIZE: usize = 16 * 1024;
|
||||
use crate::jmap::{account::JmapAccount, session::JmapAuth};
|
||||
|
||||
/// Export a raw RFC 5322 message to stdout (Email/get + blob download).
|
||||
///
|
||||
@@ -29,51 +23,30 @@ pub struct JmapEmailExportCommand {
|
||||
impl JmapEmailExportCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, account: JmapAccount) -> Result<()> {
|
||||
let tls = account.backend.tls.clone().try_into()?;
|
||||
let mut jmap = account.new_jmap_session()?;
|
||||
let auth: JmapAuth = account.backend.auth.clone().try_into()?;
|
||||
let http_auth: SecretString = auth.into();
|
||||
|
||||
let mut client = account.new_jmap_client()?;
|
||||
|
||||
let properties = Some(vec!["id".to_owned(), "blobId".to_owned()]);
|
||||
let output = client.email_get(vec![self.id.clone()], properties, false, false, 0)?;
|
||||
|
||||
let mut coroutine = JmapEmailGet::new(
|
||||
&jmap.session,
|
||||
&jmap.http_auth,
|
||||
vec![self.id.clone()],
|
||||
properties,
|
||||
false,
|
||||
false,
|
||||
0,
|
||||
)?;
|
||||
let mut buf = [0u8; READ_BUFFER_SIZE];
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
let emails = loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
JmapEmailGetResult::Ok { emails, .. } => break emails,
|
||||
JmapEmailGetResult::WantsRead => {
|
||||
let n = jmap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
JmapEmailGetResult::WantsWrite(bytes) => {
|
||||
jmap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
JmapEmailGetResult::Err(err) => bail!("{err}"),
|
||||
}
|
||||
};
|
||||
|
||||
let account_id = jmap
|
||||
.session
|
||||
let session = client.session().expect("session loaded by new_jmap_client");
|
||||
let api_url = session.api_url.clone();
|
||||
let account_id = session
|
||||
.primary_accounts
|
||||
.get(MAIL)
|
||||
.map(|s| s.as_str())
|
||||
.unwrap_or("");
|
||||
let blob_id = emails
|
||||
|
||||
let blob_id = output
|
||||
.emails
|
||||
.into_iter()
|
||||
.next()
|
||||
.and_then(|e| e.blob_id)
|
||||
.ok_or_else(|| anyhow!("Email `{}` not found or has no blobId", self.id))?;
|
||||
|
||||
let mut url: Url = jmap
|
||||
.session
|
||||
let download_url: Url = session
|
||||
.download_url
|
||||
.replace("{accountId}", account_id)
|
||||
.replace("{blobId}", &blob_id)
|
||||
@@ -81,33 +54,21 @@ impl JmapEmailExportCommand {
|
||||
.replace("{name}", "message.eml")
|
||||
.parse()?;
|
||||
|
||||
let mut extra_stream = jmap.connect_if_different(&url, &tls)?;
|
||||
let mut coroutine = JmapBlobDownload::new(&jmap.http_auth, &url);
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
let data = loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
JmapBlobDownloadResult::Ok { data, .. } => break data,
|
||||
JmapBlobDownloadResult::WantsRead => {
|
||||
let stream = extra_stream.as_mut().unwrap_or(&mut jmap.stream);
|
||||
let n = stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
JmapBlobDownloadResult::WantsWrite(bytes) => {
|
||||
let stream = extra_stream.as_mut().unwrap_or(&mut jmap.stream);
|
||||
stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
JmapBlobDownloadResult::WantsRedirect { url: new_url, .. } => {
|
||||
url = new_url;
|
||||
extra_stream = jmap.connect_if_different(&url, &tls)?;
|
||||
coroutine = JmapBlobDownload::new(&jmap.http_auth, &url);
|
||||
arg = None;
|
||||
}
|
||||
JmapBlobDownloadResult::Err(err) => bail!("{err}"),
|
||||
}
|
||||
let data = if same_authority(&api_url, &download_url) {
|
||||
client.blob_download(&download_url)?
|
||||
} else {
|
||||
let host = download_url.host_str().unwrap_or("localhost");
|
||||
let port = download_url.port_or_known_default().unwrap_or(443);
|
||||
let tcp = TcpStream::connect((host, port))?;
|
||||
let stream = upgrade_tls(host, tcp, &tls, &[b"http/1.1"])?;
|
||||
let mut download_client = JmapClient::new(stream, http_auth);
|
||||
download_client.blob_download(&download_url)?
|
||||
};
|
||||
|
||||
printer.out(Message::new(String::from_utf8(data)?))
|
||||
}
|
||||
}
|
||||
|
||||
fn same_authority(a: &Url, b: &Url) -> bool {
|
||||
a.host() == b.host() && a.port_or_known_default() == b.port_or_known_default()
|
||||
}
|
||||
|
||||
+6
-39
@@ -1,15 +1,10 @@
|
||||
use std::io::{Read, Write};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use io_jmap::rfc8621::email_get::{JmapEmailGet, JmapEmailGetResult};
|
||||
use log::warn;
|
||||
use pimalaya_toolbox::terminal::printer::Printer;
|
||||
use pimalaya_cli::printer::Printer;
|
||||
|
||||
use crate::jmap::{account::JmapAccount, email::query::EmailsTable};
|
||||
|
||||
const READ_BUFFER_SIZE: usize = 16 * 1024;
|
||||
|
||||
/// Get JMAP emails by ID (Email/get).
|
||||
///
|
||||
/// Fetches and displays email envelopes as a table.
|
||||
@@ -22,45 +17,17 @@ pub struct JmapEmailGetCommand {
|
||||
|
||||
impl JmapEmailGetCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, account: JmapAccount) -> Result<()> {
|
||||
let mut jmap = account.new_jmap_session()?;
|
||||
let mut client = account.new_jmap_client()?;
|
||||
let output = client.email_get(self.ids.clone(), None, false, false, 0)?;
|
||||
|
||||
let mut coroutine = JmapEmailGet::new(
|
||||
&jmap.session,
|
||||
&jmap.http_auth,
|
||||
self.ids.clone(),
|
||||
None,
|
||||
false,
|
||||
false,
|
||||
0,
|
||||
)?;
|
||||
let mut buf = [0u8; READ_BUFFER_SIZE];
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
let (emails, not_found) = loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
JmapEmailGetResult::Ok {
|
||||
emails, not_found, ..
|
||||
} => break (emails, not_found),
|
||||
JmapEmailGetResult::WantsRead => {
|
||||
let n = jmap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
JmapEmailGetResult::WantsWrite(bytes) => {
|
||||
jmap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
JmapEmailGetResult::Err(err) => bail!("{err}"),
|
||||
}
|
||||
};
|
||||
|
||||
for id in not_found {
|
||||
for id in output.not_found {
|
||||
warn!("email `{id}` not found, ignoring it");
|
||||
}
|
||||
|
||||
let table = EmailsTable {
|
||||
preset: account.table_preset,
|
||||
arrangement: account.table_arrangement,
|
||||
emails,
|
||||
emails: output.emails,
|
||||
};
|
||||
|
||||
printer.out(table)
|
||||
|
||||
+35
-55
@@ -1,24 +1,21 @@
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
io::{stdin, BufRead, IsTerminal, Read, Write},
|
||||
io::{stdin, BufRead, IsTerminal},
|
||||
net::TcpStream,
|
||||
};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use clap::Parser;
|
||||
use io_jmap::{
|
||||
rfc8620::blob_upload::{JmapBlobUpload, JmapBlobUploadResult},
|
||||
rfc8621::{
|
||||
capabilities::MAIL,
|
||||
email::EmailImport,
|
||||
email_import::{JmapEmailImport, JmapEmailImportResult},
|
||||
},
|
||||
client::JmapClient,
|
||||
rfc8621::{capabilities::MAIL, email::EmailImport},
|
||||
};
|
||||
use pimalaya_toolbox::terminal::printer::{Message, Printer};
|
||||
use pimalaya_cli::printer::{Message, Printer};
|
||||
use pimalaya_stream::tls::upgrade_tls;
|
||||
use secrecy::SecretString;
|
||||
use url::Url;
|
||||
|
||||
use crate::jmap::{account::JmapAccount, error::format_set_error};
|
||||
|
||||
const READ_BUFFER_SIZE: usize = 16 * 1024;
|
||||
use crate::jmap::{account::JmapAccount, error::format_set_error, session::JmapAuth};
|
||||
|
||||
/// Import an RFC 5322 message into a mailbox (upload + Email/import).
|
||||
///
|
||||
@@ -51,7 +48,10 @@ pub struct JmapEmailImportCommand {
|
||||
impl JmapEmailImportCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, account: JmapAccount) -> Result<()> {
|
||||
let tls = account.backend.tls.clone().try_into()?;
|
||||
let mut jmap = account.new_jmap_session()?;
|
||||
let auth: JmapAuth = account.backend.auth.clone().try_into()?;
|
||||
let http_auth: SecretString = auth.into();
|
||||
|
||||
let mut client = account.new_jmap_client()?;
|
||||
|
||||
let data: Vec<u8> = if stdin().is_terminal() || printer.is_json() {
|
||||
self.message
|
||||
@@ -64,39 +64,31 @@ impl JmapEmailImportCommand {
|
||||
lines.join("\r\n").into_bytes()
|
||||
};
|
||||
|
||||
let account_id = jmap
|
||||
.session
|
||||
let session = client.session().expect("session loaded by new_jmap_client");
|
||||
let api_url = session.api_url.clone();
|
||||
let account_id = session
|
||||
.primary_accounts
|
||||
.get(MAIL)
|
||||
.map(|s| s.as_str())
|
||||
.unwrap_or("");
|
||||
let url: Url = jmap
|
||||
.session
|
||||
let upload_url: Url = session
|
||||
.upload_url
|
||||
.replace("{accountId}", account_id)
|
||||
.parse()?;
|
||||
|
||||
let mut extra_stream = jmap.connect_if_different(&url, &tls)?;
|
||||
|
||||
let mut coroutine = JmapBlobUpload::new(&jmap.http_auth, &url, "message/rfc822", data);
|
||||
let mut buf = [0u8; READ_BUFFER_SIZE];
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
let blob_id = loop {
|
||||
let stream = extra_stream.as_mut().unwrap_or(&mut jmap.stream);
|
||||
|
||||
match coroutine.resume(arg.take()) {
|
||||
JmapBlobUploadResult::Ok { blob_id, .. } => break blob_id,
|
||||
JmapBlobUploadResult::WantsRead => {
|
||||
let n = stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
JmapBlobUploadResult::WantsWrite(bytes) => {
|
||||
stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
JmapBlobUploadResult::Err(err) => bail!("{err}"),
|
||||
}
|
||||
let blob_id = if same_authority(&api_url, &upload_url) {
|
||||
client
|
||||
.blob_upload(&upload_url, "message/rfc822", data)?
|
||||
.blob_id
|
||||
} else {
|
||||
let host = upload_url.host_str().unwrap_or("localhost");
|
||||
let port = upload_url.port_or_known_default().unwrap_or(443);
|
||||
let tcp = TcpStream::connect((host, port))?;
|
||||
let stream = upgrade_tls(host, tcp, &tls, &[b"http/1.1"])?;
|
||||
let mut upload_client = JmapClient::new(stream, http_auth);
|
||||
upload_client
|
||||
.blob_upload(&upload_url, "message/rfc822", data)?
|
||||
.blob_id
|
||||
};
|
||||
|
||||
if self.upload_only {
|
||||
@@ -122,25 +114,9 @@ impl JmapEmailImportCommand {
|
||||
let mut emails = BTreeMap::new();
|
||||
emails.insert(blob_id.clone(), import);
|
||||
|
||||
let mut coroutine = JmapEmailImport::new(&jmap.session, &jmap.http_auth, emails)?;
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
let output = client.email_import(emails)?;
|
||||
|
||||
let not_created = loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
JmapEmailImportResult::Ok { not_created, .. } => break not_created,
|
||||
JmapEmailImportResult::WantsRead => {
|
||||
let n = jmap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
JmapEmailImportResult::WantsWrite(bytes) => {
|
||||
jmap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
JmapEmailImportResult::Err(err) => bail!("{err}"),
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(err) = not_created.get(&blob_id) {
|
||||
if let Some(err) = output.not_created.get(&blob_id) {
|
||||
let mut msg = format!("Import JMAP email from blob `{blob_id}` error");
|
||||
msg.push_str(&format_set_error(err));
|
||||
bail!(msg);
|
||||
@@ -149,3 +125,7 @@ impl JmapEmailImportCommand {
|
||||
printer.out(Message::new("Email successfully imported"))
|
||||
}
|
||||
}
|
||||
|
||||
fn same_authority(a: &Url, b: &Url) -> bool {
|
||||
a.host() == b.host() && a.port_or_known_default() == b.port_or_known_default()
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
pub mod command;
|
||||
pub mod cli;
|
||||
pub mod copy;
|
||||
pub mod delete;
|
||||
pub mod export;
|
||||
|
||||
+7
-36
@@ -1,16 +1,11 @@
|
||||
use std::io::{Read, Write};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use io_jmap::rfc8621::email_parse::{JmapEmailParse, JmapEmailParseResult};
|
||||
use log::warn;
|
||||
use pimalaya_toolbox::terminal::printer::Printer;
|
||||
use pimalaya_cli::printer::Printer;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::jmap::account::JmapAccount;
|
||||
|
||||
const READ_BUFFER_SIZE: usize = 16 * 1024;
|
||||
|
||||
/// Parse RFC 5322 message blobs without storing them (Email/parse).
|
||||
///
|
||||
/// Useful for reading attached .eml files or message blobs that are
|
||||
@@ -24,44 +19,20 @@ pub struct JmapEmailParseCommand {
|
||||
|
||||
impl JmapEmailParseCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, account: JmapAccount) -> Result<()> {
|
||||
let mut jmap = account.new_jmap_session()?;
|
||||
let mut client = account.new_jmap_client()?;
|
||||
let output = client.email_parse(self.blob_ids.clone(), None)?;
|
||||
|
||||
let mut coroutine =
|
||||
JmapEmailParse::new(&jmap.session, &jmap.http_auth, self.blob_ids.clone(), None)?;
|
||||
let mut buf = [0u8; READ_BUFFER_SIZE];
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
let (parsed, not_parsable, not_found) = loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
JmapEmailParseResult::Ok {
|
||||
parsed,
|
||||
not_parsable,
|
||||
not_found,
|
||||
..
|
||||
} => break (parsed, not_parsable, not_found),
|
||||
JmapEmailParseResult::WantsRead => {
|
||||
let n = jmap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
JmapEmailParseResult::WantsWrite(bytes) => {
|
||||
jmap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
JmapEmailParseResult::Err(err) => bail!("{err}"),
|
||||
}
|
||||
};
|
||||
|
||||
for id in not_found {
|
||||
for id in output.not_found {
|
||||
warn!("blob `{id}` not found, ignoring it");
|
||||
}
|
||||
|
||||
for id in not_parsable {
|
||||
for id in output.not_parsable {
|
||||
warn!("blob `{id}` not valid MIME message, ignoring it");
|
||||
}
|
||||
|
||||
let mut bodies = Vec::new();
|
||||
|
||||
for (_blob_id, email) in parsed {
|
||||
for (_blob_id, email) in output.parsed {
|
||||
if let Some(body_values) = &email.body_values {
|
||||
if let Some(text_parts) = &email.text_body {
|
||||
for part in text_parts {
|
||||
|
||||
+8
-33
@@ -1,22 +1,16 @@
|
||||
use std::{
|
||||
fmt,
|
||||
io::{Read, Write},
|
||||
};
|
||||
use std::fmt;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use anyhow::Result;
|
||||
use clap::{Parser, ValueEnum};
|
||||
use comfy_table::{Cell, ContentArrangement, Row, Table};
|
||||
use io_jmap::rfc8621::{
|
||||
email::{Email, EmailAddress, EmailComparator, EmailFilter, EmailSortProperty},
|
||||
email_query::{JmapEmailQuery, JmapEmailQueryResult},
|
||||
use io_jmap::rfc8621::email::{
|
||||
Email, EmailAddress, EmailComparator, EmailFilter, EmailSortProperty,
|
||||
};
|
||||
use pimalaya_toolbox::terminal::printer::Printer;
|
||||
use pimalaya_cli::printer::Printer;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::jmap::account::JmapAccount;
|
||||
|
||||
const READ_BUFFER_SIZE: usize = 16 * 1024;
|
||||
|
||||
/// Query JMAP emails (Email/query + Email/get).
|
||||
///
|
||||
/// Lists, filters and sorts email envelopes.
|
||||
@@ -93,7 +87,7 @@ pub struct JmapEmailQueryCommand {
|
||||
|
||||
impl JmapEmailQueryCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, account: JmapAccount) -> Result<()> {
|
||||
let mut jmap = account.new_jmap_session()?;
|
||||
let mut client = account.new_jmap_client()?;
|
||||
|
||||
let filter = {
|
||||
let f = EmailFilter {
|
||||
@@ -145,37 +139,18 @@ impl JmapEmailQueryCommand {
|
||||
keyword: None,
|
||||
}]);
|
||||
|
||||
let mut coroutine = JmapEmailQuery::new(
|
||||
&jmap.session,
|
||||
&jmap.http_auth,
|
||||
let output = client.email_query(
|
||||
filter,
|
||||
sort,
|
||||
Some(self.page.saturating_sub(1) * self.page_size),
|
||||
Some(self.page_size),
|
||||
None,
|
||||
)?;
|
||||
let mut buf = [0u8; READ_BUFFER_SIZE];
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
let emails = loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
JmapEmailQueryResult::Ok { emails, .. } => break emails,
|
||||
JmapEmailQueryResult::WantsRead => {
|
||||
let n = jmap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
JmapEmailQueryResult::WantsWrite(bytes) => {
|
||||
jmap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
JmapEmailQueryResult::Err(err) => bail!("{err}"),
|
||||
}
|
||||
};
|
||||
|
||||
let table = EmailsTable {
|
||||
preset: account.table_preset,
|
||||
arrangement: account.table_arrangement,
|
||||
emails,
|
||||
emails: output.emails,
|
||||
};
|
||||
|
||||
printer.out(table)
|
||||
|
||||
+7
-42
@@ -1,18 +1,11 @@
|
||||
use std::io::{Read, Write};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use io_jmap::rfc8621::{
|
||||
email::EmailAddress,
|
||||
email_get::{JmapEmailGet, JmapEmailGetResult},
|
||||
};
|
||||
use io_jmap::rfc8621::email::EmailAddress;
|
||||
use log::warn;
|
||||
use pimalaya_toolbox::terminal::printer::{Message, Printer};
|
||||
use pimalaya_cli::printer::{Message, Printer};
|
||||
|
||||
use crate::jmap::account::JmapAccount;
|
||||
|
||||
const READ_BUFFER_SIZE: usize = 16 * 1024;
|
||||
|
||||
/// Read the content of a JMAP email (Email/get with body).
|
||||
///
|
||||
/// Shows headers and plain text body by default.
|
||||
@@ -29,44 +22,16 @@ pub struct JmapEmailReadCommand {
|
||||
|
||||
impl JmapEmailReadCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, account: JmapAccount) -> Result<()> {
|
||||
let mut jmap = account.new_jmap_session()?;
|
||||
let mut client = account.new_jmap_client()?;
|
||||
let output = client.email_get(self.ids.clone(), None, !self.html, self.html, 0)?;
|
||||
|
||||
let mut coroutine = JmapEmailGet::new(
|
||||
&jmap.session,
|
||||
&jmap.http_auth,
|
||||
self.ids.clone(),
|
||||
None,
|
||||
!self.html,
|
||||
self.html,
|
||||
0,
|
||||
)?;
|
||||
let mut buf = [0u8; READ_BUFFER_SIZE];
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
let (emails, not_found) = loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
JmapEmailGetResult::Ok {
|
||||
emails, not_found, ..
|
||||
} => break (emails, not_found),
|
||||
JmapEmailGetResult::WantsRead => {
|
||||
let n = jmap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
JmapEmailGetResult::WantsWrite(bytes) => {
|
||||
jmap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
JmapEmailGetResult::Err(err) => bail!("{err}"),
|
||||
}
|
||||
};
|
||||
|
||||
for id in not_found {
|
||||
for id in output.not_found {
|
||||
warn!("email `{id}` not found, ignoring it");
|
||||
}
|
||||
|
||||
let mut content = String::new();
|
||||
|
||||
for email in &emails {
|
||||
for email in &output.emails {
|
||||
if self.html {
|
||||
if let Some(body_values) = &email.body_values {
|
||||
if let Some(html_parts) = &email.html_body {
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
io::{Read, Write},
|
||||
};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use clap::Parser;
|
||||
use io_jmap::rfc8621::email_set::{JmapEmailSet, JmapEmailSetArgs, JmapEmailSetResult};
|
||||
use pimalaya_toolbox::terminal::printer::{Message, Printer};
|
||||
use io_jmap::rfc8621::email_set::JmapEmailSetArgs;
|
||||
use pimalaya_cli::printer::{Message, Printer};
|
||||
|
||||
use crate::jmap::{account::JmapAccount, error::format_set_error};
|
||||
|
||||
const READ_BUFFER_SIZE: usize = 16 * 1024;
|
||||
|
||||
/// Update JMAP emails via patch operations (Email/set).
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct JmapEmailUpdateCommand {
|
||||
@@ -46,7 +41,7 @@ pub struct JmapEmailUpdateCommand {
|
||||
|
||||
impl JmapEmailUpdateCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, account: JmapAccount) -> Result<()> {
|
||||
let mut jmap = account.new_jmap_session()?;
|
||||
let mut client = account.new_jmap_client()?;
|
||||
let mut args = JmapEmailSetArgs::default();
|
||||
|
||||
for id in &self.ids {
|
||||
@@ -78,29 +73,12 @@ impl JmapEmailUpdateCommand {
|
||||
}
|
||||
}
|
||||
|
||||
let mut coroutine = JmapEmailSet::new(&jmap.session, &jmap.http_auth, args)?;
|
||||
let mut buf = [0u8; READ_BUFFER_SIZE];
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
let output = client.email_set(args)?;
|
||||
|
||||
let not_updated = loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
JmapEmailSetResult::Ok { not_updated, .. } => break not_updated,
|
||||
JmapEmailSetResult::WantsRead => {
|
||||
let n = jmap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
JmapEmailSetResult::WantsWrite(bytes) => {
|
||||
jmap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
JmapEmailSetResult::Err(err) => bail!("{err}"),
|
||||
}
|
||||
};
|
||||
|
||||
if !not_updated.is_empty() {
|
||||
if !output.not_updated.is_empty() {
|
||||
let mut msg = String::from("Update JMAP email(s) error");
|
||||
|
||||
for (id, err) in not_updated {
|
||||
for (id, err) in output.not_updated {
|
||||
msg.push_str(&format!("\n `{id}`"));
|
||||
msg.push_str(&format_set_error(&err));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user