move id mapper from lib to CLI

This commit is contained in:
Clément DOUIN 2023-05-14 21:41:31 +02:00
parent 53538e36f9
commit 0ff77b5179
No known key found for this signature in database
GPG key ID: 353E4A18EE0FAB72
13 changed files with 429 additions and 168 deletions

12
Cargo.lock generated
View file

@ -1110,11 +1110,13 @@ dependencies = [
"erased-serde",
"indicatif",
"log",
"md5",
"once_cell",
"pimalaya-email",
"pimalaya-keyring",
"pimalaya-process",
"pimalaya-secret",
"rusqlite",
"serde",
"serde_json",
"shellexpand",
@ -2066,7 +2068,7 @@ dependencies = [
[[package]]
name = "pimalaya-email"
version = "0.7.1"
source = "git+https://git.sr.ht/~soywod/pimalaya#4fc9ef469ddb89728c42b13bbff928266307ed9b"
source = "git+https://git.sr.ht/~soywod/pimalaya#715546fabf7246a7f4bff8371aff77f116ae9b9a"
dependencies = [
"advisory-lock",
"ammonia",
@ -2108,7 +2110,7 @@ dependencies = [
[[package]]
name = "pimalaya-keyring"
version = "0.0.1"
source = "git+https://git.sr.ht/~soywod/pimalaya#4fc9ef469ddb89728c42b13bbff928266307ed9b"
source = "git+https://git.sr.ht/~soywod/pimalaya#715546fabf7246a7f4bff8371aff77f116ae9b9a"
dependencies = [
"keyring",
"log",
@ -2118,7 +2120,7 @@ dependencies = [
[[package]]
name = "pimalaya-oauth2"
version = "0.0.1"
source = "git+https://git.sr.ht/~soywod/pimalaya#4fc9ef469ddb89728c42b13bbff928266307ed9b"
source = "git+https://git.sr.ht/~soywod/pimalaya#715546fabf7246a7f4bff8371aff77f116ae9b9a"
dependencies = [
"log",
"oauth2",
@ -2130,7 +2132,7 @@ dependencies = [
[[package]]
name = "pimalaya-process"
version = "0.0.1"
source = "git+https://git.sr.ht/~soywod/pimalaya#4fc9ef469ddb89728c42b13bbff928266307ed9b"
source = "git+https://git.sr.ht/~soywod/pimalaya#715546fabf7246a7f4bff8371aff77f116ae9b9a"
dependencies = [
"log",
"thiserror",
@ -2139,7 +2141,7 @@ dependencies = [
[[package]]
name = "pimalaya-secret"
version = "0.0.1"
source = "git+https://git.sr.ht/~soywod/pimalaya#4fc9ef469ddb89728c42b13bbff928266307ed9b"
source = "git+https://git.sr.ht/~soywod/pimalaya#715546fabf7246a7f4bff8371aff77f116ae9b9a"
dependencies = [
"log",
"pimalaya-keyring",

View file

@ -50,11 +50,16 @@ env_logger = "0.8"
erased-serde = "0.3"
indicatif = "0.17"
log = "0.4"
md5 = "0.7.0"
once_cell = "1.16.0"
pimalaya-email = { git = "https://git.sr.ht/~soywod/pimalaya" }
pimalaya-keyring = { git = "https://git.sr.ht/~soywod/pimalaya" }
pimalaya-process = { git = "https://git.sr.ht/~soywod/pimalaya" }
pimalaya-secret = { git = "https://git.sr.ht/~soywod/pimalaya" }
# pimalaya-email = { path = "/home/soywod/sourcehut/pimalaya/email" }
# pimalaya-keyring = { path = "/home/soywod/sourcehut/pimalaya/keyring" }
# pimalaya-process = { path = "/home/soywod/sourcehut/pimalaya/process" }
# pimalaya-secret = { path = "/home/soywod/sourcehut/pimalaya/secret" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
shellexpand = "2.1"
@ -64,3 +69,8 @@ toml = "0.7.2"
unicode-width = "0.1"
url = "2.2"
uuid = { version = "0.8", features = ["v4"] }
[target.'cfg(target_env = "musl")'.dependencies]
rusqlite = { version = "0.29", features = [] }
[target.'cfg(not(target_env = "musl"))'.dependencies]
rusqlite = { version = "0.29", features = ["bundled"] }

View file

@ -1,11 +1,4 @@
[toolchain]
channel = "stable"
profile = "default"
components = [
"cargo",
"clippy",
"rust-analyzer",
"rust-std",
"rustc",
"rustfmt",
]
components = [ "rust-src", "rust-analyzer" ]

187
src/cache/id_mapper.rs vendored Normal file
View file

@ -0,0 +1,187 @@
use anyhow::{anyhow, Context, Result};
use log::{debug, trace};
#[cfg(feature = "imap-backend")]
use pimalaya_email::ImapBackend;
#[cfg(feature = "notmuch-backend")]
use pimalaya_email::NotmuchBackend;
use pimalaya_email::{Backend, MaildirBackend};
use std::path::{Path, PathBuf};
const ID_MAPPER_DB_FILE_NAME: &str = ".id-mapper.sqlite";
#[derive(Debug)]
pub enum IdMapper {
Dummy,
Mapper(String, rusqlite::Connection),
}
impl IdMapper {
fn find_closest_db_path<D>(dir: D) -> PathBuf
where
D: AsRef<Path>,
{
let mut db_path = dir.as_ref().join(ID_MAPPER_DB_FILE_NAME);
let mut db_parent_dir = dir.as_ref().parent();
while !db_path.is_file() {
match db_parent_dir {
Some(dir) => {
db_path = dir.join(ID_MAPPER_DB_FILE_NAME);
db_parent_dir = dir.parent();
}
None => {
db_path = dir.as_ref().join(ID_MAPPER_DB_FILE_NAME);
break;
}
}
}
db_path
}
pub fn new(backend: &dyn Backend, account: &str, folder: &str) -> Result<Self> {
let mut db_path = PathBuf::default();
if let Some(backend) = backend.as_any().downcast_ref::<MaildirBackend>() {
db_path = Self::find_closest_db_path(&backend.path());
}
#[cfg(feature = "imap-backend")]
if backend.as_any().is::<ImapBackend>() {
return Ok(Self::Dummy);
}
#[cfg(feature = "notmuch-backend")]
if let Some(backend) = backend.as_any().downcast_ref::<NotmuchBackend>() {
db_path = Self::find_closest_db_path(&backend.path());
}
let digest = md5::compute(account.to_string() + folder);
let table = format!("id_mapper_{digest:x}");
debug!("creating id mapper table {table} at {db_path:?}…");
let conn = rusqlite::Connection::open(&db_path)
.with_context(|| format!("cannot open id mapper database at {db_path:?}"))?;
let query = format!(
"CREATE TABLE IF NOT EXISTS {table} (
id INTEGER PRIMARY KEY AUTOINCREMENT,
internal_id TEXT UNIQUE
)",
);
trace!("create table query: {query:#?}");
conn.execute(&query, [])
.context("cannot create id mapper table")?;
Ok(Self::Mapper(table, conn))
}
pub fn create_alias<I>(&self, id: I) -> Result<String>
where
I: AsRef<str>,
{
let id = id.as_ref();
match self {
Self::Dummy => Ok(id.to_owned()),
Self::Mapper(table, conn) => {
debug!("creating alias for id {id}…");
let query = format!("INSERT OR IGNORE INTO {} (internal_id) VALUES (?)", table);
trace!("insert query: {query:#?}");
conn.execute(&query, [id])
.with_context(|| format!("cannot create id alias for id {id}"))?;
let alias = conn.last_insert_rowid().to_string();
debug!("created alias {alias} for id {id}");
Ok(alias)
}
}
}
pub fn get_or_create_alias<I>(&self, id: I) -> Result<String>
where
I: AsRef<str>,
{
let id = id.as_ref();
match self {
Self::Dummy => Ok(id.to_owned()),
Self::Mapper(table, conn) => {
debug!("getting alias for id {id}…");
let query = format!("SELECT id FROM {} WHERE internal_id = ?", table);
trace!("select query: {query:#?}");
let mut stmt = conn
.prepare(&query)
.with_context(|| format!("cannot get alias for id {id}"))?;
let aliases: Vec<i64> = stmt
.query_map([id], |row| row.get(0))
.with_context(|| format!("cannot get alias for id {id}"))?
.collect::<rusqlite::Result<_>>()
.with_context(|| format!("cannot get alias for id {id}"))?;
let alias = match aliases.first() {
Some(alias) => {
debug!("found alias {alias} for id {id}");
alias.to_string()
}
None => {
debug!("alias not found, creating it…");
self.create_alias(id)?
}
};
Ok(alias)
}
}
}
pub fn get_id<A>(&self, alias: A) -> Result<String>
where
A: AsRef<str>,
{
let alias = alias.as_ref();
let alias = alias
.parse::<i64>()
.context(format!("cannot parse id mapper alias {alias}"))?;
match self {
Self::Dummy => Ok(alias.to_string()),
Self::Mapper(table, conn) => {
debug!("getting id from alias {alias}…");
let query = format!("SELECT internal_id FROM {} WHERE id = ?", table);
trace!("select query: {query:#?}");
let mut stmt = conn
.prepare(&query)
.with_context(|| format!("cannot get id from alias {alias}"))?;
let ids: Vec<String> = stmt
.query_map([alias], |row| row.get(0))
.with_context(|| format!("cannot get id from alias {alias}"))?
.collect::<rusqlite::Result<_>>()
.with_context(|| format!("cannot get id from alias {alias}"))?;
let id = ids
.first()
.ok_or_else(|| anyhow!("cannot get id from alias {alias}"))?
.to_owned();
debug!("found id {id} from alias {alias}");
Ok(id)
}
}
}
pub fn get_ids<A, I>(&self, aliases: I) -> Result<Vec<String>>
where
A: AsRef<str>,
I: IntoIterator<Item = A>,
{
aliases
.into_iter()
.map(|alias| self.get_id(alias))
.collect()
}
}

3
src/cache/mod.rs vendored
View file

@ -1 +1,4 @@
pub mod args;
mod id_mapper;
pub use id_mapper::IdMapper;

View file

@ -14,17 +14,20 @@ use uuid::Uuid;
use crate::{
printer::{PrintTableOpts, Printer},
ui::editor,
Envelopes,
Envelopes, IdMapper,
};
pub fn attachments<P: Printer, B: Backend + ?Sized>(
pub fn attachments<P: Printer>(
config: &AccountConfig,
printer: &mut P,
backend: &mut B,
id_mapper: &IdMapper,
backend: &mut dyn Backend,
folder: &str,
ids: Vec<&str>,
) -> Result<()> {
let folder = config.folder_alias(folder)?;
let ids = id_mapper.get_ids(ids)?;
let ids = ids.iter().map(String::as_str).collect::<Vec<_>>();
let emails = backend.get_emails(&folder, ids.clone())?;
let mut index = 0;
@ -71,45 +74,54 @@ pub fn attachments<P: Printer, B: Backend + ?Sized>(
}
}
pub fn copy<P: Printer, B: Backend + ?Sized>(
pub fn copy<P: Printer>(
config: &AccountConfig,
printer: &mut P,
backend: &mut B,
id_mapper: &IdMapper,
backend: &mut dyn Backend,
from_folder: &str,
to_folder: &str,
ids: Vec<&str>,
) -> Result<()> {
let from_folder = config.folder_alias(from_folder)?;
let to_folder = config.folder_alias(to_folder)?;
let ids = id_mapper.get_ids(ids)?;
let ids = ids.iter().map(String::as_str).collect::<Vec<_>>();
backend.copy_emails(&from_folder, &to_folder, ids)?;
printer.print("Email(s) successfully copied!")
}
pub fn delete<P: Printer, B: Backend + ?Sized>(
pub fn delete<P: Printer>(
config: &AccountConfig,
printer: &mut P,
backend: &mut B,
id_mapper: &IdMapper,
backend: &mut dyn Backend,
folder: &str,
ids: Vec<&str>,
) -> Result<()> {
let folder = config.folder_alias(folder)?;
let ids = id_mapper.get_ids(ids)?;
let ids = ids.iter().map(String::as_str).collect::<Vec<_>>();
backend.delete_emails(&folder, ids)?;
printer.print("Email(s) successfully deleted!")
}
pub fn forward<P: Printer, B: Backend + ?Sized, S: Sender + ?Sized>(
pub fn forward<P: Printer>(
config: &AccountConfig,
printer: &mut P,
backend: &mut B,
sender: &mut S,
id_mapper: &IdMapper,
backend: &mut dyn Backend,
sender: &mut dyn Sender,
folder: &str,
id: &str,
headers: Option<Vec<&str>>,
body: Option<&str>,
) -> Result<()> {
let folder = config.folder_alias(folder)?;
let ids = id_mapper.get_ids([id])?;
let ids = ids.iter().map(String::as_str).collect::<Vec<_>>();
let tpl = backend
.get_emails(&folder, vec![id])?
.get_emails(&folder, ids)?
.first()
.ok_or_else(|| anyhow!("cannot find email {}", id))?
.to_forward_tpl_builder(config)?
@ -121,10 +133,11 @@ pub fn forward<P: Printer, B: Backend + ?Sized, S: Sender + ?Sized>(
Ok(())
}
pub fn list<P: Printer, B: Backend + ?Sized>(
pub fn list<P: Printer>(
config: &AccountConfig,
printer: &mut P,
backend: &mut B,
id_mapper: &IdMapper,
backend: &mut dyn Backend,
folder: &str,
max_width: Option<usize>,
page_size: Option<usize>,
@ -133,8 +146,11 @@ pub fn list<P: Printer, B: Backend + ?Sized>(
let folder = config.folder_alias(folder)?;
let page_size = page_size.unwrap_or(config.email_listing_page_size());
debug!("page size: {}", page_size);
let envelopes: Envelopes = backend.list_envelopes(&folder, page_size, page)?.into();
let mut envelopes: Envelopes = backend.list_envelopes(&folder, page_size, page)?.into();
envelopes.remap_ids(id_mapper)?;
trace!("envelopes: {:?}", envelopes);
printer.print_table(
Box::new(envelopes),
PrintTableOpts {
@ -147,11 +163,11 @@ pub fn list<P: Printer, B: Backend + ?Sized>(
/// Parses and edits a message from a [mailto] URL string.
///
/// [mailto]: https://en.wikipedia.org/wiki/Mailto
pub fn mailto<P: Printer, B: Backend + ?Sized, S: Sender + ?Sized>(
pub fn mailto<P: Printer>(
config: &AccountConfig,
printer: &mut P,
backend: &mut B,
sender: &mut S,
backend: &mut dyn Backend,
sender: &mut dyn Sender,
url: &Url,
) -> Result<()> {
let mut tpl = TplBuilder::default().to(url.path());
@ -169,24 +185,28 @@ pub fn mailto<P: Printer, B: Backend + ?Sized, S: Sender + ?Sized>(
editor::edit_tpl_with_editor(config, printer, backend, sender, tpl.build())
}
pub fn move_<P: Printer, B: Backend + ?Sized>(
pub fn move_<P: Printer>(
config: &AccountConfig,
printer: &mut P,
backend: &mut B,
id_mapper: &IdMapper,
backend: &mut dyn Backend,
from_folder: &str,
to_folder: &str,
ids: Vec<&str>,
) -> Result<()> {
let from_folder = config.folder_alias(from_folder)?;
let to_folder = config.folder_alias(to_folder)?;
let ids = id_mapper.get_ids(ids)?;
let ids = ids.iter().map(String::as_str).collect::<Vec<_>>();
backend.move_emails(&from_folder, &to_folder, ids)?;
printer.print("Email(s) successfully moved!")
}
pub fn read<P: Printer, B: Backend + ?Sized>(
pub fn read<P: Printer>(
config: &AccountConfig,
printer: &mut P,
backend: &mut B,
id_mapper: &IdMapper,
backend: &mut dyn Backend,
folder: &str,
ids: Vec<&str>,
text_mime: &str,
@ -195,6 +215,8 @@ pub fn read<P: Printer, B: Backend + ?Sized>(
headers: Vec<&str>,
) -> Result<()> {
let folder = config.folder_alias(folder)?;
let ids = id_mapper.get_ids(ids)?;
let ids = ids.iter().map(String::as_str).collect::<Vec<_>>();
let emails = backend.get_emails(&folder, ids)?;
let mut glue = "";
@ -230,11 +252,12 @@ pub fn read<P: Printer, B: Backend + ?Sized>(
printer.print(bodies)
}
pub fn reply<P: Printer, B: Backend + ?Sized, S: Sender + ?Sized>(
pub fn reply<P: Printer>(
config: &AccountConfig,
printer: &mut P,
backend: &mut B,
sender: &mut S,
id_mapper: &IdMapper,
backend: &mut dyn Backend,
sender: &mut dyn Sender,
folder: &str,
id: &str,
all: bool,
@ -242,8 +265,10 @@ pub fn reply<P: Printer, B: Backend + ?Sized, S: Sender + ?Sized>(
body: Option<&str>,
) -> Result<()> {
let folder = config.folder_alias(folder)?;
let ids = id_mapper.get_ids([id])?;
let ids = ids.iter().map(String::as_str).collect::<Vec<_>>();
let tpl = backend
.get_emails(&folder, vec![id])?
.get_emails(&folder, ids)?
.first()
.ok_or_else(|| anyhow!("cannot find email {}", id))?
.to_reply_tpl_builder(config, all)?
@ -256,10 +281,11 @@ pub fn reply<P: Printer, B: Backend + ?Sized, S: Sender + ?Sized>(
Ok(())
}
pub fn save<P: Printer, B: Backend + ?Sized>(
pub fn save<P: Printer>(
config: &AccountConfig,
printer: &mut P,
backend: &mut B,
id_mapper: &IdMapper,
backend: &mut dyn Backend,
folder: &str,
raw_email: String,
) -> Result<()> {
@ -276,14 +302,18 @@ pub fn save<P: Printer, B: Backend + ?Sized>(
.collect::<Vec<String>>()
.join("\r\n")
};
backend.add_email(&folder, raw_email.as_bytes(), &Flags::default())?;
let id = backend.add_email(&folder, raw_email.as_bytes(), &Flags::default())?;
id_mapper.create_alias(id)?;
Ok(())
}
pub fn search<P: Printer, B: Backend + ?Sized>(
pub fn search<P: Printer>(
config: &AccountConfig,
printer: &mut P,
backend: &mut B,
id_mapper: &IdMapper,
backend: &mut dyn Backend,
folder: &str,
query: String,
max_width: Option<usize>,
@ -292,9 +322,10 @@ pub fn search<P: Printer, B: Backend + ?Sized>(
) -> Result<()> {
let folder = config.folder_alias(folder)?;
let page_size = page_size.unwrap_or(config.email_listing_page_size());
let envelopes: Envelopes = backend
let mut envelopes: Envelopes = backend
.search_envelopes(&folder, &query, "", page_size, page)?
.into();
envelopes.remap_ids(id_mapper)?;
let opts = PrintTableOpts {
format: &config.email_reading_format,
max_width,
@ -303,10 +334,11 @@ pub fn search<P: Printer, B: Backend + ?Sized>(
printer.print_table(Box::new(envelopes), opts)
}
pub fn sort<P: Printer, B: Backend + ?Sized>(
pub fn sort<P: Printer>(
config: &AccountConfig,
printer: &mut P,
backend: &mut B,
id_mapper: &IdMapper,
backend: &mut dyn Backend,
folder: &str,
sort: String,
query: String,
@ -316,9 +348,10 @@ pub fn sort<P: Printer, B: Backend + ?Sized>(
) -> Result<()> {
let folder = config.folder_alias(folder)?;
let page_size = page_size.unwrap_or(config.email_listing_page_size());
let envelopes: Envelopes = backend
let mut envelopes: Envelopes = backend
.search_envelopes(&folder, &query, &sort, page_size, page)?
.into();
envelopes.remap_ids(id_mapper)?;
let opts = PrintTableOpts {
format: &config.email_reading_format,
max_width,
@ -327,11 +360,11 @@ pub fn sort<P: Printer, B: Backend + ?Sized>(
printer.print_table(Box::new(envelopes), opts)
}
pub fn send<P: Printer, B: Backend + ?Sized, S: Sender + ?Sized>(
pub fn send<P: Printer>(
config: &AccountConfig,
printer: &mut P,
backend: &mut B,
sender: &mut S,
backend: &mut dyn Backend,
sender: &mut dyn Sender,
raw_email: String,
) -> Result<()> {
let folder = config.sent_folder_alias()?;
@ -357,11 +390,11 @@ pub fn send<P: Printer, B: Backend + ?Sized, S: Sender + ?Sized>(
Ok(())
}
pub fn write<P: Printer, B: Backend + ?Sized, S: Sender + ?Sized>(
pub fn write<P: Printer>(
config: &AccountConfig,
printer: &mut P,
backend: &mut B,
sender: &mut S,
backend: &mut dyn Backend,
sender: &mut dyn Sender,
headers: Option<Vec<&str>>,
body: Option<&str>,
) -> Result<()> {

View file

@ -1,18 +1,26 @@
use std::ops;
use anyhow::Result;
use serde::Serialize;
use std::ops;
use crate::{
printer::{PrintTable, PrintTableOpts, WriteColor},
ui::Table,
Envelope,
Envelope, IdMapper,
};
/// Represents the list of envelopes.
#[derive(Clone, Debug, Default, Serialize)]
pub struct Envelopes(Vec<Envelope>);
impl Envelopes {
pub fn remap_ids(&mut self, id_mapper: &IdMapper) -> Result<()> {
for envelope in &mut self.0 {
envelope.id = id_mapper.get_or_create_alias(&envelope.id)?;
}
Ok(())
}
}
impl ops::Deref for Envelopes {
type Target = Vec<Envelope>;

View file

@ -1,37 +1,46 @@
use anyhow::Result;
use pimalaya_email::{Backend, Flags};
use crate::printer::Printer;
use crate::{printer::Printer, IdMapper};
pub fn add<P: Printer, B: Backend + ?Sized>(
pub fn add<P: Printer>(
printer: &mut P,
backend: &mut B,
id_mapper: &IdMapper,
backend: &mut dyn Backend,
folder: &str,
ids: Vec<&str>,
flags: &Flags,
) -> Result<()> {
let ids = id_mapper.get_ids(ids)?;
let ids = ids.iter().map(String::as_str).collect::<Vec<_>>();
backend.add_flags(folder, ids, flags)?;
printer.print("Flag(s) successfully added!")
}
pub fn set<P: Printer, B: Backend + ?Sized>(
pub fn set<P: Printer>(
printer: &mut P,
backend: &mut B,
id_mapper: &IdMapper,
backend: &mut dyn Backend,
folder: &str,
ids: Vec<&str>,
flags: &Flags,
) -> Result<()> {
let ids = id_mapper.get_ids(ids)?;
let ids = ids.iter().map(String::as_str).collect::<Vec<_>>();
backend.set_flags(folder, ids, flags)?;
printer.print("Flag(s) successfully set!")
}
pub fn remove<P: Printer, B: Backend + ?Sized>(
pub fn remove<P: Printer>(
printer: &mut P,
backend: &mut B,
id_mapper: &IdMapper,
backend: &mut dyn Backend,
folder: &str,
ids: Vec<&str>,
flags: &Flags,
) -> Result<()> {
let ids = id_mapper.get_ids(ids)?;
let ids = ids.iter().map(String::as_str).collect::<Vec<_>>();
backend.remove_flags(folder, ids, flags)?;
printer.print("Flag(s) successfully removed!")
}

View file

@ -12,19 +12,15 @@ use crate::{
Folders,
};
pub fn expunge<P: Printer, B: Backend + ?Sized>(
printer: &mut P,
backend: &mut B,
folder: &str,
) -> Result<()> {
pub fn expunge<P: Printer>(printer: &mut P, backend: &mut dyn Backend, folder: &str) -> Result<()> {
backend.expunge_folder(folder)?;
printer.print(format!("Folder {folder} successfully expunged!"))
}
pub fn list<P: Printer, B: Backend + ?Sized>(
pub fn list<P: Printer>(
config: &AccountConfig,
printer: &mut P,
backend: &mut B,
backend: &mut dyn Backend,
max_width: Option<usize>,
) -> Result<()> {
let folders: Folders = backend.list_folders()?.into();
@ -38,20 +34,12 @@ pub fn list<P: Printer, B: Backend + ?Sized>(
)
}
pub fn create<P: Printer, B: Backend + ?Sized>(
printer: &mut P,
backend: &mut B,
folder: &str,
) -> Result<()> {
pub fn create<P: Printer>(printer: &mut P, backend: &mut dyn Backend, folder: &str) -> Result<()> {
backend.add_folder(folder)?;
printer.print("Folder successfully created!")
}
pub fn delete<P: Printer, B: Backend + ?Sized>(
printer: &mut P,
backend: &mut B,
folder: &str,
) -> Result<()> {
pub fn delete<P: Printer>(printer: &mut P, backend: &mut dyn Backend, folder: &str) -> Result<()> {
if let Some(false) | None = Confirm::new()
.with_prompt(format!("Confirm deletion of folder {folder}?"))
.default(false)
@ -176,9 +164,6 @@ mod tests {
fn get_envelope(&self, _: &str, _: &str) -> backend::Result<Envelope> {
unimplemented!();
}
fn get_envelope_internal(&self, _: &str, _: &str) -> backend::Result<Envelope> {
unimplemented!();
}
fn list_envelopes(&self, _: &str, _: usize, _: usize) -> backend::Result<Envelopes> {
unimplemented!()
}
@ -195,60 +180,31 @@ mod tests {
fn add_email(&self, _: &str, _: &[u8], _: &Flags) -> backend::Result<String> {
unimplemented!()
}
fn add_email_internal(&self, _: &str, _: &[u8], _: &Flags) -> backend::Result<String> {
unimplemented!()
}
fn get_emails(&self, _: &str, _: Vec<&str>) -> backend::Result<Emails> {
unimplemented!()
}
fn preview_emails(&self, _: &str, _: Vec<&str>) -> backend::Result<Emails> {
unimplemented!()
}
fn get_emails_internal(&self, _: &str, _: Vec<&str>) -> backend::Result<Emails> {
unimplemented!()
}
fn copy_emails(&self, _: &str, _: &str, _: Vec<&str>) -> backend::Result<()> {
unimplemented!()
}
fn copy_emails_internal(&self, _: &str, _: &str, _: Vec<&str>) -> backend::Result<()> {
unimplemented!()
}
fn move_emails(&self, _: &str, _: &str, _: Vec<&str>) -> backend::Result<()> {
unimplemented!()
}
fn move_emails_internal(&self, _: &str, _: &str, _: Vec<&str>) -> backend::Result<()> {
unimplemented!()
}
fn delete_emails(&self, _: &str, _: Vec<&str>) -> backend::Result<()> {
unimplemented!()
}
fn delete_emails_internal(&self, _: &str, _: Vec<&str>) -> backend::Result<()> {
unimplemented!()
}
fn add_flags(&self, _: &str, _: Vec<&str>, _: &Flags) -> backend::Result<()> {
unimplemented!()
}
fn add_flags_internal(&self, _: &str, _: Vec<&str>, _: &Flags) -> backend::Result<()> {
unimplemented!()
}
fn set_flags(&self, _: &str, _: Vec<&str>, _: &Flags) -> backend::Result<()> {
unimplemented!()
}
fn set_flags_internal(&self, _: &str, _: Vec<&str>, _: &Flags) -> backend::Result<()> {
unimplemented!()
}
fn remove_flags(&self, _: &str, _: Vec<&str>, _: &Flags) -> backend::Result<()> {
unimplemented!()
}
fn remove_flags_internal(
&self,
_: &str,
_: Vec<&str>,
_: &Flags,
) -> backend::Result<()> {
unimplemented!()
}
fn as_any(&'static self) -> &(dyn Any) {
fn as_any(&self) -> &(dyn Any) {
self
}
}

View file

@ -3,19 +3,22 @@ use atty::Stream;
use pimalaya_email::{AccountConfig, Backend, CompilerBuilder, Email, Flags, Sender, Tpl};
use std::io::{stdin, BufRead};
use crate::printer::Printer;
use crate::{printer::Printer, IdMapper};
pub fn forward<P: Printer, B: Backend + ?Sized>(
pub fn forward<P: Printer>(
config: &AccountConfig,
printer: &mut P,
backend: &mut B,
id_mapper: &IdMapper,
backend: &mut dyn Backend,
folder: &str,
id: &str,
headers: Option<Vec<&str>>,
body: Option<&str>,
) -> Result<()> {
let ids = id_mapper.get_ids([id])?;
let ids = ids.iter().map(String::as_str).collect::<Vec<_>>();
let tpl = backend
.get_emails(folder, vec![id])?
.get_emails(folder, ids)?
.first()
.ok_or_else(|| anyhow!("cannot find email {}", id))?
.to_forward_tpl_builder(config)?
@ -26,18 +29,21 @@ pub fn forward<P: Printer, B: Backend + ?Sized>(
printer.print(<Tpl as Into<String>>::into(tpl))
}
pub fn reply<P: Printer, B: Backend + ?Sized>(
pub fn reply<P: Printer>(
config: &AccountConfig,
printer: &mut P,
backend: &mut B,
id_mapper: &IdMapper,
backend: &mut dyn Backend,
folder: &str,
id: &str,
all: bool,
headers: Option<Vec<&str>>,
body: Option<&str>,
) -> Result<()> {
let ids = id_mapper.get_ids([id])?;
let ids = ids.iter().map(String::as_str).collect::<Vec<_>>();
let tpl = backend
.get_emails(folder, vec![id])?
.get_emails(folder, ids)?
.first()
.ok_or_else(|| anyhow!("cannot find email {}", id))?
.to_reply_tpl_builder(config, all)?
@ -48,10 +54,11 @@ pub fn reply<P: Printer, B: Backend + ?Sized>(
printer.print(<Tpl as Into<String>>::into(tpl))
}
pub fn save<P: Printer, B: Backend + ?Sized>(
pub fn save<P: Printer>(
config: &AccountConfig,
printer: &mut P,
backend: &mut B,
id_mapper: &IdMapper,
backend: &mut dyn Backend,
folder: &str,
tpl: String,
) -> Result<()> {
@ -71,15 +78,17 @@ pub fn save<P: Printer, B: Backend + ?Sized>(
.some_pgp_encrypt_cmd(config.email_writing_encrypt_cmd.as_ref()),
)?;
backend.add_email(folder, &email, &Flags::default())?;
let id = backend.add_email(folder, &email, &Flags::default())?;
id_mapper.create_alias(id)?;
printer.print("Template successfully saved!")
}
pub fn send<P: Printer, B: Backend + ?Sized, S: Sender + ?Sized>(
pub fn send<P: Printer>(
config: &AccountConfig,
printer: &mut P,
backend: &mut B,
sender: &mut S,
backend: &mut dyn Backend,
sender: &mut dyn Sender,
folder: &str,
tpl: String,
) -> Result<()> {
@ -114,6 +123,5 @@ pub fn write<'a, P: Printer>(
.set_some_raw_headers(headers)
.some_text_plain_part(body)
.build();
printer.print(<Tpl as Into<String>>::into(tpl))
}

View file

@ -7,4 +7,5 @@ pub mod output;
pub mod printer;
pub mod ui;
pub use cache::*;
pub use domain::*;

View file

@ -1,6 +1,9 @@
use anyhow::{anyhow, Context, Result};
use clap::Command;
use std::{borrow::Cow, env};
use pimalaya_email::{
BackendBuilder, BackendConfig, ImapBackend, SenderBuilder, DEFAULT_INBOX_FOLDER,
};
use std::env;
use url::Url;
use himalaya::{
@ -8,10 +11,7 @@ use himalaya::{
config::{self, DeserializedConfig},
email, flag, folder, man, output,
printer::StdoutPrinter,
tpl,
};
use pimalaya_email::{
BackendBuilder, BackendConfig, ImapBackend, SenderBuilder, DEFAULT_INBOX_FOLDER,
tpl, IdMapper,
};
#[cfg(feature = "imap-backend")]
@ -52,7 +52,8 @@ fn main() -> Result<()> {
let url = Url::parse(&raw_args[1])?;
let config = DeserializedConfig::from_opt_path(None)?;
let (account_config, backend_config) = config.to_configs(None)?;
let mut backend = BackendBuilder::new().build(&account_config, &backend_config)?;
let mut backend =
BackendBuilder::new().build(account_config.clone(), backend_config.clone())?;
let mut sender = SenderBuilder::build(&account_config)?;
let mut printer = StdoutPrinter::default();
@ -99,13 +100,11 @@ fn main() -> Result<()> {
// recreating an instance.
match imap::args::matches(&m)? {
Some(imap::args::Cmd::Notify(keepalive)) => {
let imap =
ImapBackend::new(Cow::Borrowed(&account_config), Cow::Borrowed(&imap_config))?;
let imap = ImapBackend::new(account_config.clone(), imap_config.clone())?;
return imap::handlers::notify(&imap, &folder, keepalive);
}
Some(imap::args::Cmd::Watch(keepalive)) => {
let imap =
ImapBackend::new(Cow::Borrowed(&account_config), Cow::Borrowed(&imap_config))?;
let imap = ImapBackend::new(account_config.clone(), imap_config.clone())?;
return imap::handlers::watch(&imap, &folder, keepalive);
}
_ => (),
@ -126,7 +125,7 @@ fn main() -> Result<()> {
let backend = BackendBuilder::new()
.sessions_pool_size(8)
.disable_cache(true)
.build(&account_config, &backend_config)?;
.build(account_config.clone(), backend_config.clone())?;
account::handlers::sync(
&account_config,
&mut printer,
@ -152,13 +151,13 @@ fn main() -> Result<()> {
let folder = account_config.folder_alias(folder)?;
let mut backend = BackendBuilder::new()
.disable_cache(disable_cache)
.build(&account_config, &backend_config)?;
.build(account_config.clone(), backend_config.clone())?;
return folder::handlers::create(&mut printer, backend.as_mut(), &folder);
}
Some(folder::args::Cmd::List(max_width)) => {
let mut backend = BackendBuilder::new()
.disable_cache(disable_cache)
.build(&account_config, &backend_config)?;
.build(account_config.clone(), backend_config.clone())?;
return folder::handlers::list(
&account_config,
&mut printer,
@ -170,7 +169,7 @@ fn main() -> Result<()> {
let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?;
let mut backend = BackendBuilder::new()
.disable_cache(disable_cache)
.build(&account_config, &backend_config)?;
.build(account_config.clone(), backend_config.clone())?;
return folder::handlers::expunge(&mut printer, backend.as_mut(), &folder);
}
Some(folder::args::Cmd::Delete) => {
@ -180,7 +179,7 @@ fn main() -> Result<()> {
let folder = account_config.folder_alias(folder)?;
let mut backend = BackendBuilder::new()
.disable_cache(disable_cache)
.build(&account_config, &backend_config)?;
.build(account_config.clone(), backend_config.clone())?;
return folder::handlers::delete(&mut printer, backend.as_mut(), &folder);
}
_ => (),
@ -192,10 +191,12 @@ fn main() -> Result<()> {
let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?;
let mut backend = BackendBuilder::new()
.disable_cache(disable_cache)
.build(&account_config, &backend_config)?;
.build(account_config.clone(), backend_config.clone())?;
let id_mapper = IdMapper::new(backend.as_ref(), &account_config.name, &folder)?;
return email::handlers::attachments(
&account_config,
&mut printer,
&id_mapper,
backend.as_mut(),
&folder,
ids,
@ -205,10 +206,12 @@ fn main() -> Result<()> {
let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?;
let mut backend = BackendBuilder::new()
.disable_cache(disable_cache)
.build(&account_config, &backend_config)?;
.build(account_config.clone(), backend_config.clone())?;
let id_mapper = IdMapper::new(backend.as_ref(), &account_config.name, &folder)?;
return email::handlers::copy(
&account_config,
&mut printer,
&id_mapper,
backend.as_mut(),
&folder,
to_folder,
@ -219,10 +222,12 @@ fn main() -> Result<()> {
let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?;
let mut backend = BackendBuilder::new()
.disable_cache(disable_cache)
.build(&account_config, &backend_config)?;
.build(account_config.clone(), backend_config.clone())?;
let id_mapper = IdMapper::new(backend.as_ref(), &account_config.name, &folder)?;
return email::handlers::delete(
&account_config,
&mut printer,
&id_mapper,
backend.as_mut(),
&folder,
ids,
@ -232,10 +237,12 @@ fn main() -> Result<()> {
let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?;
let mut backend = BackendBuilder::new()
.disable_cache(disable_cache)
.build(&account_config, &backend_config)?;
.build(account_config.clone(), backend_config.clone())?;
let id_mapper = IdMapper::new(backend.as_ref(), &account_config.name, &folder)?;
return email::handlers::forward(
&account_config,
&mut printer,
&id_mapper,
backend.as_mut(),
sender.as_mut(),
&folder,
@ -248,10 +255,12 @@ fn main() -> Result<()> {
let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?;
let mut backend = BackendBuilder::new()
.disable_cache(disable_cache)
.build(&account_config, &backend_config)?;
.build(account_config.clone(), backend_config.clone())?;
let id_mapper = IdMapper::new(backend.as_ref(), &account_config.name, &folder)?;
return email::handlers::list(
&account_config,
&mut printer,
&id_mapper,
backend.as_mut(),
&folder,
max_width,
@ -263,10 +272,12 @@ fn main() -> Result<()> {
let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?;
let mut backend = BackendBuilder::new()
.disable_cache(disable_cache)
.build(&account_config, &backend_config)?;
.build(account_config.clone(), backend_config.clone())?;
let id_mapper = IdMapper::new(backend.as_ref(), &account_config.name, &folder)?;
return email::handlers::move_(
&account_config,
&mut printer,
&id_mapper,
backend.as_mut(),
&folder,
to_folder,
@ -277,10 +288,12 @@ fn main() -> Result<()> {
let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?;
let mut backend = BackendBuilder::new()
.disable_cache(disable_cache)
.build(&account_config, &backend_config)?;
.build(account_config.clone(), backend_config.clone())?;
let id_mapper = IdMapper::new(backend.as_ref(), &account_config.name, &folder)?;
return email::handlers::read(
&account_config,
&mut printer,
&id_mapper,
backend.as_mut(),
&folder,
ids,
@ -294,10 +307,12 @@ fn main() -> Result<()> {
let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?;
let mut backend = BackendBuilder::new()
.disable_cache(disable_cache)
.build(&account_config, &backend_config)?;
.build(account_config.clone(), backend_config.clone())?;
let id_mapper = IdMapper::new(backend.as_ref(), &account_config.name, &folder)?;
return email::handlers::reply(
&account_config,
&mut printer,
&id_mapper,
backend.as_mut(),
sender.as_mut(),
&folder,
@ -311,10 +326,12 @@ fn main() -> Result<()> {
let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?;
let mut backend = BackendBuilder::new()
.disable_cache(disable_cache)
.build(&account_config, &backend_config)?;
.build(account_config.clone(), backend_config.clone())?;
let id_mapper = IdMapper::new(backend.as_ref(), &account_config.name, &folder)?;
return email::handlers::save(
&account_config,
&mut printer,
&id_mapper,
backend.as_mut(),
&folder,
raw_email,
@ -324,10 +341,12 @@ fn main() -> Result<()> {
let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?;
let mut backend = BackendBuilder::new()
.disable_cache(disable_cache)
.build(&account_config, &backend_config)?;
.build(account_config.clone(), backend_config.clone())?;
let id_mapper = IdMapper::new(backend.as_ref(), &account_config.name, &folder)?;
return email::handlers::search(
&account_config,
&mut printer,
&id_mapper,
backend.as_mut(),
&folder,
query,
@ -340,10 +359,12 @@ fn main() -> Result<()> {
let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?;
let mut backend = BackendBuilder::new()
.disable_cache(disable_cache)
.build(&account_config, &backend_config)?;
.build(account_config.clone(), backend_config.clone())?;
let id_mapper = IdMapper::new(backend.as_ref(), &account_config.name, &folder)?;
return email::handlers::sort(
&account_config,
&mut printer,
&id_mapper,
backend.as_mut(),
&folder,
criteria,
@ -356,7 +377,7 @@ fn main() -> Result<()> {
Some(email::args::Cmd::Send(raw_email)) => {
let mut backend = BackendBuilder::new()
.disable_cache(disable_cache)
.build(&account_config, &backend_config)?;
.build(account_config.clone(), backend_config.clone())?;
return email::handlers::send(
&account_config,
&mut printer,
@ -370,22 +391,46 @@ fn main() -> Result<()> {
let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?;
let mut backend = BackendBuilder::new()
.disable_cache(disable_cache)
.build(&account_config, &backend_config)?;
return flag::handlers::set(&mut printer, backend.as_mut(), &folder, ids, flags);
.build(account_config.clone(), backend_config.clone())?;
let id_mapper = IdMapper::new(backend.as_ref(), &account_config.name, &folder)?;
return flag::handlers::set(
&mut printer,
&id_mapper,
backend.as_mut(),
&folder,
ids,
flags,
);
}
Some(flag::args::Cmd::Add(ids, ref flags)) => {
let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?;
let mut backend = BackendBuilder::new()
.disable_cache(disable_cache)
.build(&account_config, &backend_config)?;
return flag::handlers::add(&mut printer, backend.as_mut(), &folder, ids, flags);
.build(account_config.clone(), backend_config.clone())?;
let id_mapper = IdMapper::new(backend.as_ref(), &account_config.name, &folder)?;
return flag::handlers::add(
&mut printer,
&id_mapper,
backend.as_mut(),
&folder,
ids,
flags,
);
}
Some(flag::args::Cmd::Remove(ids, ref flags)) => {
let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?;
let mut backend = BackendBuilder::new()
.disable_cache(disable_cache)
.build(&account_config, &backend_config)?;
return flag::handlers::remove(&mut printer, backend.as_mut(), &folder, ids, flags);
.build(account_config.clone(), backend_config.clone())?;
let id_mapper = IdMapper::new(backend.as_ref(), &account_config.name, &folder)?;
return flag::handlers::remove(
&mut printer,
&id_mapper,
backend.as_mut(),
&folder,
ids,
flags,
);
}
_ => (),
},
@ -394,10 +439,12 @@ fn main() -> Result<()> {
let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?;
let mut backend = BackendBuilder::new()
.disable_cache(disable_cache)
.build(&account_config, &backend_config)?;
.build(account_config.clone(), backend_config.clone())?;
let id_mapper = IdMapper::new(backend.as_ref(), &account_config.name, &folder)?;
return tpl::handlers::forward(
&account_config,
&mut printer,
&id_mapper,
backend.as_mut(),
&folder,
id,
@ -412,10 +459,12 @@ fn main() -> Result<()> {
let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?;
let mut backend = BackendBuilder::new()
.disable_cache(disable_cache)
.build(&account_config, &backend_config)?;
.build(account_config.clone(), backend_config.clone())?;
let id_mapper = IdMapper::new(backend.as_ref(), &account_config.name, &folder)?;
return tpl::handlers::reply(
&account_config,
&mut printer,
&id_mapper,
backend.as_mut(),
&folder,
id,
@ -428,10 +477,12 @@ fn main() -> Result<()> {
let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?;
let mut backend = BackendBuilder::new()
.disable_cache(disable_cache)
.build(&account_config, &backend_config)?;
.build(account_config.clone(), backend_config.clone())?;
let id_mapper = IdMapper::new(backend.as_ref(), &account_config.name, &folder)?;
return tpl::handlers::save(
&account_config,
&mut printer,
&id_mapper,
backend.as_mut(),
&folder,
tpl,
@ -441,7 +492,7 @@ fn main() -> Result<()> {
let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?;
let mut backend = BackendBuilder::new()
.disable_cache(disable_cache)
.build(&account_config, &backend_config)?;
.build(account_config.clone(), backend_config.clone())?;
return tpl::handlers::send(
&account_config,
&mut printer,
@ -456,7 +507,7 @@ fn main() -> Result<()> {
Some(email::args::Cmd::Write(headers, body)) => {
let mut backend = BackendBuilder::new()
.disable_cache(disable_cache)
.build(&account_config, &backend_config)?;
.build(account_config.clone(), backend_config.clone())?;
return email::handlers::write(
&account_config,
&mut printer,

View file

@ -37,11 +37,11 @@ pub fn open_with_local_draft() -> Result<Tpl> {
open_with_tpl(Tpl::from(content))
}
pub fn edit_tpl_with_editor<P: Printer, B: Backend + ?Sized, S: Sender + ?Sized>(
pub fn edit_tpl_with_editor<P: Printer>(
config: &AccountConfig,
printer: &mut P,
backend: &mut B,
sender: &mut S,
backend: &mut dyn Backend,
sender: &mut dyn Sender,
mut tpl: Tpl,
) -> Result<()> {
let draft = local_draft_path();