diff --git a/Cargo.lock b/Cargo.lock index 2ab5185..8200cd9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index f9d157c..d8c4700 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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"] } diff --git a/rust-toolchain.toml b/rust-toolchain.toml index aff8cc2..3ccec48 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,11 +1,4 @@ [toolchain] channel = "stable" profile = "default" -components = [ - "cargo", - "clippy", - "rust-analyzer", - "rust-std", - "rustc", - "rustfmt", -] +components = [ "rust-src", "rust-analyzer" ] diff --git a/src/cache/id_mapper.rs b/src/cache/id_mapper.rs new file mode 100644 index 0000000..4e5e251 --- /dev/null +++ b/src/cache/id_mapper.rs @@ -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(dir: D) -> PathBuf + where + D: AsRef, + { + 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 { + let mut db_path = PathBuf::default(); + + if let Some(backend) = backend.as_any().downcast_ref::() { + db_path = Self::find_closest_db_path(&backend.path()); + } + + #[cfg(feature = "imap-backend")] + if backend.as_any().is::() { + return Ok(Self::Dummy); + } + + #[cfg(feature = "notmuch-backend")] + if let Some(backend) = backend.as_any().downcast_ref::() { + 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(&self, id: I) -> Result + where + I: AsRef, + { + 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(&self, id: I) -> Result + where + I: AsRef, + { + 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 = stmt + .query_map([id], |row| row.get(0)) + .with_context(|| format!("cannot get alias for id {id}"))? + .collect::>() + .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(&self, alias: A) -> Result + where + A: AsRef, + { + let alias = alias.as_ref(); + let alias = alias + .parse::() + .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 = stmt + .query_map([alias], |row| row.get(0)) + .with_context(|| format!("cannot get id from alias {alias}"))? + .collect::>() + .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(&self, aliases: I) -> Result> + where + A: AsRef, + I: IntoIterator, + { + aliases + .into_iter() + .map(|alias| self.get_id(alias)) + .collect() + } +} diff --git a/src/cache/mod.rs b/src/cache/mod.rs index 6e10f4a..2dae5fe 100644 --- a/src/cache/mod.rs +++ b/src/cache/mod.rs @@ -1 +1,4 @@ pub mod args; +mod id_mapper; + +pub use id_mapper::IdMapper; diff --git a/src/domain/email/handlers.rs b/src/domain/email/handlers.rs index 6ddca41..13f4e10 100644 --- a/src/domain/email/handlers.rs +++ b/src/domain/email/handlers.rs @@ -14,17 +14,20 @@ use uuid::Uuid; use crate::{ printer::{PrintTableOpts, Printer}, ui::editor, - Envelopes, + Envelopes, IdMapper, }; -pub fn attachments( +pub fn attachments( 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::>(); let emails = backend.get_emails(&folder, ids.clone())?; let mut index = 0; @@ -71,45 +74,54 @@ pub fn attachments( } } -pub fn copy( +pub fn copy( 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::>(); backend.copy_emails(&from_folder, &to_folder, ids)?; printer.print("Email(s) successfully copied!") } -pub fn delete( +pub fn delete( 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::>(); backend.delete_emails(&folder, ids)?; printer.print("Email(s) successfully deleted!") } -pub fn forward( +pub fn forward( 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>, 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::>(); 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( Ok(()) } -pub fn list( +pub fn list( config: &AccountConfig, printer: &mut P, - backend: &mut B, + id_mapper: &IdMapper, + backend: &mut dyn Backend, folder: &str, max_width: Option, page_size: Option, @@ -133,8 +146,11 @@ pub fn list( 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( /// Parses and edits a message from a [mailto] URL string. /// /// [mailto]: https://en.wikipedia.org/wiki/Mailto -pub fn mailto( +pub fn mailto( 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( editor::edit_tpl_with_editor(config, printer, backend, sender, tpl.build()) } -pub fn move_( +pub fn move_( 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::>(); backend.move_emails(&from_folder, &to_folder, ids)?; printer.print("Email(s) successfully moved!") } -pub fn read( +pub fn read( 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( 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::>(); let emails = backend.get_emails(&folder, ids)?; let mut glue = ""; @@ -230,11 +252,12 @@ pub fn read( printer.print(bodies) } -pub fn reply( +pub fn reply( 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( 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::>(); 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( Ok(()) } -pub fn save( +pub fn save( 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( .collect::>() .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( +pub fn search( config: &AccountConfig, printer: &mut P, - backend: &mut B, + id_mapper: &IdMapper, + backend: &mut dyn Backend, folder: &str, query: String, max_width: Option, @@ -292,9 +322,10 @@ pub fn search( ) -> 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( printer.print_table(Box::new(envelopes), opts) } -pub fn sort( +pub fn sort( 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( ) -> 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( printer.print_table(Box::new(envelopes), opts) } -pub fn send( +pub fn send( 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( Ok(()) } -pub fn write( +pub fn write( config: &AccountConfig, printer: &mut P, - backend: &mut B, - sender: &mut S, + backend: &mut dyn Backend, + sender: &mut dyn Sender, headers: Option>, body: Option<&str>, ) -> Result<()> { diff --git a/src/domain/envelope/envelopes.rs b/src/domain/envelope/envelopes.rs index 3efe2d3..f3352e0 100644 --- a/src/domain/envelope/envelopes.rs +++ b/src/domain/envelope/envelopes.rs @@ -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); +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; diff --git a/src/domain/flag/handlers.rs b/src/domain/flag/handlers.rs index 0e184f7..7381b69 100644 --- a/src/domain/flag/handlers.rs +++ b/src/domain/flag/handlers.rs @@ -1,37 +1,46 @@ use anyhow::Result; use pimalaya_email::{Backend, Flags}; -use crate::printer::Printer; +use crate::{printer::Printer, IdMapper}; -pub fn add( +pub fn add( 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::>(); backend.add_flags(folder, ids, flags)?; printer.print("Flag(s) successfully added!") } -pub fn set( +pub fn set( 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::>(); backend.set_flags(folder, ids, flags)?; printer.print("Flag(s) successfully set!") } -pub fn remove( +pub fn remove( 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::>(); backend.remove_flags(folder, ids, flags)?; printer.print("Flag(s) successfully removed!") } diff --git a/src/domain/folder/handlers.rs b/src/domain/folder/handlers.rs index b433ddc..598df98 100644 --- a/src/domain/folder/handlers.rs +++ b/src/domain/folder/handlers.rs @@ -12,19 +12,15 @@ use crate::{ Folders, }; -pub fn expunge( - printer: &mut P, - backend: &mut B, - folder: &str, -) -> Result<()> { +pub fn expunge(printer: &mut P, backend: &mut dyn Backend, folder: &str) -> Result<()> { backend.expunge_folder(folder)?; printer.print(format!("Folder {folder} successfully expunged!")) } -pub fn list( +pub fn list( config: &AccountConfig, printer: &mut P, - backend: &mut B, + backend: &mut dyn Backend, max_width: Option, ) -> Result<()> { let folders: Folders = backend.list_folders()?.into(); @@ -38,20 +34,12 @@ pub fn list( ) } -pub fn create( - printer: &mut P, - backend: &mut B, - folder: &str, -) -> Result<()> { +pub fn create(printer: &mut P, backend: &mut dyn Backend, folder: &str) -> Result<()> { backend.add_folder(folder)?; printer.print("Folder successfully created!") } -pub fn delete( - printer: &mut P, - backend: &mut B, - folder: &str, -) -> Result<()> { +pub fn delete(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 { unimplemented!(); } - fn get_envelope_internal(&self, _: &str, _: &str) -> backend::Result { - unimplemented!(); - } fn list_envelopes(&self, _: &str, _: usize, _: usize) -> backend::Result { unimplemented!() } @@ -195,60 +180,31 @@ mod tests { fn add_email(&self, _: &str, _: &[u8], _: &Flags) -> backend::Result { unimplemented!() } - fn add_email_internal(&self, _: &str, _: &[u8], _: &Flags) -> backend::Result { - unimplemented!() - } fn get_emails(&self, _: &str, _: Vec<&str>) -> backend::Result { unimplemented!() } fn preview_emails(&self, _: &str, _: Vec<&str>) -> backend::Result { unimplemented!() } - fn get_emails_internal(&self, _: &str, _: Vec<&str>) -> backend::Result { - 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 } } diff --git a/src/domain/tpl/handlers.rs b/src/domain/tpl/handlers.rs index 9f09b3b..d74561e 100644 --- a/src/domain/tpl/handlers.rs +++ b/src/domain/tpl/handlers.rs @@ -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( +pub fn forward( config: &AccountConfig, printer: &mut P, - backend: &mut B, + id_mapper: &IdMapper, + backend: &mut dyn Backend, folder: &str, id: &str, headers: Option>, body: Option<&str>, ) -> Result<()> { + let ids = id_mapper.get_ids([id])?; + let ids = ids.iter().map(String::as_str).collect::>(); 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( printer.print(>::into(tpl)) } -pub fn reply( +pub fn reply( config: &AccountConfig, printer: &mut P, - backend: &mut B, + id_mapper: &IdMapper, + backend: &mut dyn Backend, folder: &str, id: &str, all: bool, headers: Option>, body: Option<&str>, ) -> Result<()> { + let ids = id_mapper.get_ids([id])?; + let ids = ids.iter().map(String::as_str).collect::>(); 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( printer.print(>::into(tpl)) } -pub fn save( +pub fn save( 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( .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( +pub fn send( 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(>::into(tpl)) } diff --git a/src/lib.rs b/src/lib.rs index 4ba6146..962bab0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,4 +7,5 @@ pub mod output; pub mod printer; pub mod ui; +pub use cache::*; pub use domain::*; diff --git a/src/main.rs b/src/main.rs index f6fff46..3553aca 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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, diff --git a/src/ui/editor.rs b/src/ui/editor.rs index 87ca7b1..795332a 100644 --- a/src/ui/editor.rs +++ b/src/ui/editor.rs @@ -37,11 +37,11 @@ pub fn open_with_local_draft() -> Result { open_with_tpl(Tpl::from(content)) } -pub fn edit_tpl_with_editor( +pub fn edit_tpl_with_editor( 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();