use std clients

This commit is contained in:
Clément DOUIN
2026-05-06 01:41:12 +02:00
parent c32047d4de
commit 8416a41f99
149 changed files with 2083 additions and 3857 deletions
@@ -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
View File
@@ -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));
}
+6 -28
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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 -1
View File
@@ -1,4 +1,4 @@
pub mod command;
pub mod cli;
pub mod copy;
pub mod delete;
pub mod export;
+7 -36
View File
@@ -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
View File
@@ -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
View File
@@ -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 {
+7 -29
View File
@@ -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));
}