forked from meli/meli
1
Fork 0

Compare commits

...

4 Commits

Author SHA1 Message Date
Manos Pitsidianakis b2ff359eb7 melib/imap: add support for CHILDREN extesion RFC 3348 2022-09-03 23:59:29 +03:00
Manos Pitsidianakis 9fcf88b494 Wrap {AccountHash,MailboxHash} type aliases in New Types wrappers 2022-09-03 23:35:24 +03:00
Manos Pitsidianakis 808bdf75a1 melib/smtp: add BINARYMIME support to smtp client
Concerns #49

IMAP: Lemonade profile tracking issue
2022-09-03 18:17:17 +03:00
Manos Pitsidianakis ed16e29de1 melib/smtp: add 8BITMIME support to smtp client
Concerns #49

IMAP: Lemonade profile tracking issue
2022-09-03 18:02:58 +03:00
21 changed files with 213 additions and 184 deletions

View File

@ -257,6 +257,27 @@ impl Backends {
} }
} }
#[derive(
Debug, Serialize, Deserialize, Clone, PartialOrd, Ord, Default, Copy, Hash, PartialEq, Eq,
)]
pub struct AccountHash(pub u64);
#[derive(
Debug, Serialize, Deserialize, Clone, PartialOrd, Ord, Default, Copy, Hash, PartialEq, Eq,
)]
pub struct MailboxHash(pub u64);
impl std::fmt::Display for AccountHash {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "AccountHash({})", self.0)
}
}
impl std::fmt::Display for MailboxHash {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "MailboxHash({})", self.0)
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum BackendEvent { pub enum BackendEvent {
Notice { Notice {
@ -589,8 +610,6 @@ pub trait BackendMailbox: Debug {
fn count(&self) -> Result<(usize, usize)>; fn count(&self) -> Result<(usize, usize)>;
} }
pub type AccountHash = u64;
pub type MailboxHash = u64;
pub type Mailbox = Box<dyn BackendMailbox + Send + Sync>; pub type Mailbox = Box<dyn BackendMailbox + Send + Sync>;
impl Clone for Mailbox { impl Clone for Mailbox {

View File

@ -66,6 +66,7 @@ pub type MessageSequenceNumber = ImapNum;
pub static SUPPORTED_CAPABILITIES: &[&str] = &[ pub static SUPPORTED_CAPABILITIES: &[&str] = &[
"AUTH=OAUTH2", "AUTH=OAUTH2",
"CHILDREN",
#[cfg(feature = "deflate_compression")] #[cfg(feature = "deflate_compression")]
"COMPRESS=DEFLATE", "COMPRESS=DEFLATE",
"CONDSTORE", "CONDSTORE",
@ -505,7 +506,7 @@ impl MailBackend for ImapType {
let account_hash = uid_store.account_hash; let account_hash = uid_store.account_hash;
main_conn_lck.add_refresh_event(RefreshEvent { main_conn_lck.add_refresh_event(RefreshEvent {
account_hash, account_hash,
mailbox_hash: 0, mailbox_hash: MailboxHash(0),
kind: RefreshEventKind::Failure(err.clone()), kind: RefreshEventKind::Failure(err.clone()),
}); });
return Err(err); return Err(err);
@ -938,7 +939,7 @@ impl MailBackend for ImapType {
} }
let ret: Result<()> = ImapResponse::try_from(response.as_slice())?.into(); let ret: Result<()> = ImapResponse::try_from(response.as_slice())?.into();
ret?; ret?;
let new_hash = get_path_hash!(path.as_str()); let new_hash = MailboxHash(get_path_hash!(path.as_str()));
uid_store.mailboxes.lock().await.clear(); uid_store.mailboxes.lock().await.clear();
Ok((new_hash, new_mailbox_fut?.await.map_err(|err| MeliError::new(format!("Mailbox create was succesful (returned `{}`) but listing mailboxes afterwards returned `{}`", String::from_utf8_lossy(&response), err)))?)) Ok((new_hash, new_mailbox_fut?.await.map_err(|err| MeliError::new(format!("Mailbox create was succesful (returned `{}`) but listing mailboxes afterwards returned `{}`", String::from_utf8_lossy(&response), err)))?))
})) }))
@ -1075,7 +1076,7 @@ impl MailBackend for ImapType {
.read_response(&mut response, RequiredResponses::empty()) .read_response(&mut response, RequiredResponses::empty())
.await?; .await?;
} }
let new_hash = get_path_hash!(new_path.as_str()); let new_hash = MailboxHash(get_path_hash!(new_path.as_str()));
let ret: Result<()> = ImapResponse::try_from(response.as_slice())?.into(); let ret: Result<()> = ImapResponse::try_from(response.as_slice())?.into();
ret?; ret?;
uid_store.mailboxes.lock().await.clear(); uid_store.mailboxes.lock().await.clear();
@ -1322,7 +1323,7 @@ impl ImapType {
let account_hash = { let account_hash = {
let mut hasher = DefaultHasher::new(); let mut hasher = DefaultHasher::new();
hasher.write(s.name.as_bytes()); hasher.write(s.name.as_bytes());
hasher.finish() AccountHash(hasher.finish())
}; };
let account_name = Arc::new(s.name().to_string()); let account_name = Arc::new(s.name().to_string());
let uid_store: Arc<UIDStore> = Arc::new(UIDStore { let uid_store: Arc<UIDStore> = Arc::new(UIDStore {
@ -1475,7 +1476,7 @@ impl ImapType {
debug!("parse error for {:?}", l); debug!("parse error for {:?}", l);
} }
} }
mailboxes.retain(|_, v| v.hash != 0); mailboxes.retain(|_, v| v.hash.0 != 0);
conn.send_command(b"LSUB \"\" \"*\"").await?; conn.send_command(b"LSUB \"\" \"*\"").await?;
conn.read_response(&mut res, RequiredResponses::LSUB_REQUIRED) conn.read_response(&mut res, RequiredResponses::LSUB_REQUIRED)
.await?; .await?;

View File

@ -177,7 +177,7 @@ mod sqlite3_m {
.prepare("SELECT MAX(uid) FROM envelopes WHERE mailbox_hash = ?1;")?; .prepare("SELECT MAX(uid) FROM envelopes WHERE mailbox_hash = ?1;")?;
let mut ret: Vec<UID> = stmt let mut ret: Vec<UID> = stmt
.query_map(sqlite3::params![mailbox_hash as i64], |row| { .query_map(sqlite3::params![mailbox_hash.0 as i64], |row| {
Ok(row.get(0).map(|i: Sqlite3UID| i as UID)?) Ok(row.get(0).map(|i: Sqlite3UID| i as UID)?)
})? })?
.collect::<std::result::Result<_, _>>()?; .collect::<std::result::Result<_, _>>()?;
@ -199,7 +199,7 @@ mod sqlite3_m {
"SELECT uidvalidity, flags, highestmodseq FROM mailbox WHERE mailbox_hash = ?1;", "SELECT uidvalidity, flags, highestmodseq FROM mailbox WHERE mailbox_hash = ?1;",
)?; )?;
let mut ret = stmt.query_map(sqlite3::params![mailbox_hash as i64], |row| { let mut ret = stmt.query_map(sqlite3::params![mailbox_hash.0 as i64], |row| {
Ok(( Ok((
row.get(0).map(|u: Sqlite3UID| u as UID)?, row.get(0).map(|u: Sqlite3UID| u as UID)?,
row.get(1)?, row.get(1)?,
@ -265,7 +265,7 @@ mod sqlite3_m {
self.connection self.connection
.execute( .execute(
"DELETE FROM mailbox WHERE mailbox_hash = ?1", "DELETE FROM mailbox WHERE mailbox_hash = ?1",
sqlite3::params![mailbox_hash as i64], sqlite3::params![mailbox_hash.0 as i64],
) )
.chain_err_summary(|| { .chain_err_summary(|| {
format!( format!(
@ -277,7 +277,7 @@ mod sqlite3_m {
if let Some(Ok(highestmodseq)) = select_response.highestmodseq { if let Some(Ok(highestmodseq)) = select_response.highestmodseq {
self.connection.execute( self.connection.execute(
"INSERT OR IGNORE INTO mailbox (uidvalidity, flags, highestmodseq, mailbox_hash) VALUES (?1, ?2, ?3, ?4)", "INSERT OR IGNORE INTO mailbox (uidvalidity, flags, highestmodseq, mailbox_hash) VALUES (?1, ?2, ?3, ?4)",
sqlite3::params![select_response.uidvalidity as Sqlite3UID, select_response.flags.1.iter().map(|s| s.as_str()).collect::<Vec<&str>>().join("\0").as_bytes(), highestmodseq, mailbox_hash as i64], sqlite3::params![select_response.uidvalidity as Sqlite3UID, select_response.flags.1.iter().map(|s| s.as_str()).collect::<Vec<&str>>().join("\0").as_bytes(), highestmodseq, mailbox_hash.0 as i64],
) )
.chain_err_summary(|| { .chain_err_summary(|| {
format!( format!(
@ -292,7 +292,7 @@ mod sqlite3_m {
sqlite3::params![ sqlite3::params![
select_response.uidvalidity as Sqlite3UID, select_response.uidvalidity as Sqlite3UID,
select_response.flags.1.iter().map(|s| s.as_str()).collect::<Vec<&str>>().join("\0").as_bytes(), select_response.flags.1.iter().map(|s| s.as_str()).collect::<Vec<&str>>().join("\0").as_bytes(),
mailbox_hash as i64 mailbox_hash.0 as i64
], ],
) )
.chain_err_summary(|| { .chain_err_summary(|| {
@ -328,7 +328,7 @@ mod sqlite3_m {
.join("\0") .join("\0")
.as_bytes(), .as_bytes(),
highestmodseq, highestmodseq,
mailbox_hash as i64 mailbox_hash.0 as i64
], ],
) )
.chain_err_summary(|| { .chain_err_summary(|| {
@ -350,7 +350,7 @@ mod sqlite3_m {
.collect::<Vec<&str>>() .collect::<Vec<&str>>()
.join("\0") .join("\0")
.as_bytes(), .as_bytes(),
mailbox_hash as i64 mailbox_hash.0 as i64
], ],
) )
.chain_err_summary(|| { .chain_err_summary(|| {
@ -374,7 +374,7 @@ mod sqlite3_m {
)?; )?;
let ret: Vec<(UID, Envelope, Option<ModSequence>)> = stmt let ret: Vec<(UID, Envelope, Option<ModSequence>)> = stmt
.query_map(sqlite3::params![mailbox_hash as i64], |row| { .query_map(sqlite3::params![mailbox_hash.0 as i64], |row| {
Ok(( Ok((
row.get(0).map(|i: Sqlite3UID| i as UID)?, row.get(0).map(|i: Sqlite3UID| i as UID)?,
row.get(1)?, row.get(1)?,
@ -452,7 +452,7 @@ mod sqlite3_m {
max_uid = std::cmp::max(max_uid, *uid); max_uid = std::cmp::max(max_uid, *uid);
tx.execute( tx.execute(
"INSERT OR REPLACE INTO envelopes (hash, uid, mailbox_hash, modsequence, envelope) VALUES (?1, ?2, ?3, ?4, ?5)", "INSERT OR REPLACE INTO envelopes (hash, uid, mailbox_hash, modsequence, envelope) VALUES (?1, ?2, ?3, ?4, ?5)",
sqlite3::params![envelope.hash() as i64, *uid as Sqlite3UID, mailbox_hash as i64, modseq, &envelope], sqlite3::params![envelope.hash() as i64, *uid as Sqlite3UID, mailbox_hash.0 as i64, modseq, &envelope],
).chain_err_summary(|| format!("Could not insert envelope {} {} in header_cache of account {}", envelope.message_id(), envelope.hash(), uid_store.account_name))?; ).chain_err_summary(|| format!("Could not insert envelope {} {} in header_cache of account {}", envelope.message_id(), envelope.hash(), uid_store.account_name))?;
} }
} }
@ -486,7 +486,7 @@ mod sqlite3_m {
hash_index_lck.remove(&env_hash); hash_index_lck.remove(&env_hash);
tx.execute( tx.execute(
"DELETE FROM envelopes WHERE mailbox_hash = ?1 AND uid = ?2;", "DELETE FROM envelopes WHERE mailbox_hash = ?1 AND uid = ?2;",
sqlite3::params![mailbox_hash as i64, *uid as Sqlite3UID], sqlite3::params![mailbox_hash.0 as i64, *uid as Sqlite3UID],
) )
.chain_err_summary(|| { .chain_err_summary(|| {
format!( format!(
@ -502,7 +502,7 @@ mod sqlite3_m {
let mut ret: Vec<Envelope> = stmt let mut ret: Vec<Envelope> = stmt
.query_map( .query_map(
sqlite3::params![mailbox_hash as i64, *uid as Sqlite3UID], sqlite3::params![mailbox_hash.0 as i64, *uid as Sqlite3UID],
|row| Ok(row.get(0)?), |row| Ok(row.get(0)?),
)? )?
.collect::<std::result::Result<_, _>>()?; .collect::<std::result::Result<_, _>>()?;
@ -512,7 +512,7 @@ mod sqlite3_m {
env.labels_mut().extend(tags.iter().map(|t| tag_hash!(t))); env.labels_mut().extend(tags.iter().map(|t| tag_hash!(t)));
tx.execute( tx.execute(
"UPDATE envelopes SET envelope = ?1 WHERE mailbox_hash = ?2 AND uid = ?3;", "UPDATE envelopes SET envelope = ?1 WHERE mailbox_hash = ?2 AND uid = ?3;",
sqlite3::params![&env, mailbox_hash as i64, *uid as Sqlite3UID], sqlite3::params![&env, mailbox_hash.0 as i64, *uid as Sqlite3UID],
) )
.chain_err_summary(|| { .chain_err_summary(|| {
format!( format!(
@ -556,7 +556,7 @@ mod sqlite3_m {
let x = stmt let x = stmt
.query_map( .query_map(
sqlite3::params![mailbox_hash as i64, uid as Sqlite3UID], sqlite3::params![mailbox_hash.0 as i64, uid as Sqlite3UID],
|row| { |row| {
Ok(( Ok((
row.get(0).map(|u: Sqlite3UID| u as UID)?, row.get(0).map(|u: Sqlite3UID| u as UID)?,
@ -575,7 +575,7 @@ mod sqlite3_m {
let x = stmt let x = stmt
.query_map( .query_map(
sqlite3::params![mailbox_hash as i64, env_hash as i64], sqlite3::params![mailbox_hash.0 as i64, env_hash as i64],
|row| { |row| {
Ok(( Ok((
row.get(0).map(|u: Sqlite3UID| u as UID)?, row.get(0).map(|u: Sqlite3UID| u as UID)?,
@ -612,7 +612,7 @@ mod sqlite3_m {
)?; )?;
let x = stmt let x = stmt
.query_map( .query_map(
sqlite3::params![mailbox_hash as i64, uid as Sqlite3UID], sqlite3::params![mailbox_hash.0 as i64, uid as Sqlite3UID],
|row| Ok(row.get(0)?), |row| Ok(row.get(0)?),
)? )?
.collect::<std::result::Result<_, _>>()?; .collect::<std::result::Result<_, _>>()?;
@ -624,7 +624,7 @@ mod sqlite3_m {
)?; )?;
let x = stmt let x = stmt
.query_map( .query_map(
sqlite3::params![mailbox_hash as i64, env_hash as i64], sqlite3::params![mailbox_hash.0 as i64, env_hash as i64],
|row| Ok(row.get(0)?), |row| Ok(row.get(0)?),
)? )?
.collect::<std::result::Result<_, _>>()?; .collect::<std::result::Result<_, _>>()?;

View File

@ -33,6 +33,7 @@ pub struct ImapMailbox {
pub path: String, pub path: String,
pub name: String, pub name: String,
pub parent: Option<MailboxHash>, pub parent: Option<MailboxHash>,
pub has_children: bool,
pub children: Vec<MailboxHash>, pub children: Vec<MailboxHash>,
pub separator: u8, pub separator: u8,
pub usage: Arc<RwLock<SpecialUsageMailbox>>, pub usage: Arc<RwLock<SpecialUsageMailbox>>,
@ -47,6 +48,12 @@ pub struct ImapMailbox {
} }
impl ImapMailbox { impl ImapMailbox {
pub fn set_has_children(&mut self, has_children: bool) -> &mut Self {
self.has_children = has_children;
self
}
pub fn imap_path(&self) -> &str { pub fn imap_path(&self) -> &str {
&self.imap_path &self.imap_path
} }
@ -88,6 +95,9 @@ impl BackendMailbox for ImapMailbox {
} }
fn children(&self) -> &[MailboxHash] { fn children(&self) -> &[MailboxHash] {
if !self.has_children {
return &[];
}
&self.children &self.children
} }
@ -106,9 +116,11 @@ impl BackendMailbox for ImapMailbox {
fn permissions(&self) -> MailboxPermissions { fn permissions(&self) -> MailboxPermissions {
*self.permissions.lock().unwrap() *self.permissions.lock().unwrap()
} }
fn is_subscribed(&self) -> bool { fn is_subscribed(&self) -> bool {
self.is_subscribed self.is_subscribed
} }
fn set_is_subscribed(&mut self, new_val: bool) -> Result<()> { fn set_is_subscribed(&mut self, new_val: bool) -> Result<()> {
self.is_subscribed = new_val; self.is_subscribed = new_val;
Ok(()) Ok(())

View File

@ -449,6 +449,7 @@ pub fn list_mailbox_result(input: &[u8]) -> IResult<&[u8], ImapMailbox> {
({ ({
let separator: u8 = separator[0]; let separator: u8 = separator[0];
let mut f = ImapMailbox::default(); let mut f = ImapMailbox::default();
f.has_children = true;
f.no_select = false; f.no_select = false;
f.is_subscribed = false; f.is_subscribed = false;
@ -475,10 +476,14 @@ pub fn list_mailbox_result(input: &[u8]) -> IResult<&[u8], ImapMailbox> {
let _ = f.set_special_usage(SpecialUsageMailbox::Flagged); let _ = f.set_special_usage(SpecialUsageMailbox::Flagged);
} else if p.eq_ignore_ascii_case(b"\\Archive") { } else if p.eq_ignore_ascii_case(b"\\Archive") {
let _ = f.set_special_usage(SpecialUsageMailbox::Archive); let _ = f.set_special_usage(SpecialUsageMailbox::Archive);
} else if p.eq_ignore_ascii_case(b"\\HasChildren") {
f.has_children = true;
} else if p.eq_ignore_ascii_case(b"\\HasNoChildren") {
f.has_children = false;
} }
} }
f.imap_path = path.to_string(); f.imap_path = path.to_string();
f.hash = get_path_hash!(&f.imap_path); f.hash = MailboxHash(get_path_hash!(&f.imap_path));
f.path = if separator == b'/' { f.path = if separator == b'/' {
f.imap_path.clone() f.imap_path.clone()
} else { } else {
@ -486,7 +491,7 @@ pub fn list_mailbox_result(input: &[u8]) -> IResult<&[u8], ImapMailbox> {
}; };
f.name = if let Some(pos) = f.imap_path.as_bytes().iter().rposition(|&c| c == separator) f.name = if let Some(pos) = f.imap_path.as_bytes().iter().rposition(|&c| c == separator)
{ {
f.parent = Some(get_path_hash!(&f.imap_path[..pos])); f.parent = Some(MailboxHash(get_path_hash!(&f.imap_path[..pos])));
f.imap_path[pos + 1..].to_string() f.imap_path[pos + 1..].to_string()
} else { } else {
f.imap_path.clone() f.imap_path.clone()
@ -1561,7 +1566,9 @@ pub struct StatusResponse {
pub fn status_response(input: &[u8]) -> IResult<&[u8], StatusResponse> { pub fn status_response(input: &[u8]) -> IResult<&[u8], StatusResponse> {
let (input, _) = tag("* STATUS ")(input)?; let (input, _) = tag("* STATUS ")(input)?;
let (input, mailbox) = take_until(" (")(input)?; let (input, mailbox) = take_until(" (")(input)?;
let mailbox = mailbox_token(mailbox).map(|(_, m)| get_path_hash!(m)).ok(); let mailbox = mailbox_token(mailbox)
.map(|(_, m)| MailboxHash(get_path_hash!(m)))
.ok();
let (input, _) = tag(" (")(input)?; let (input, _) = tag(" (")(input)?;
let (input, result) = permutation(( let (input, result) = permutation((
opt(preceded( opt(preceded(

View File

@ -138,8 +138,11 @@ impl MaildirMailbox {
settings: &AccountSettings, settings: &AccountSettings,
) -> Result<Self> { ) -> Result<Self> {
let pathbuf = PathBuf::from(&path); let pathbuf = PathBuf::from(&path);
let mut h = DefaultHasher::new(); let hash = {
pathbuf.hash(&mut h); let mut h = DefaultHasher::new();
pathbuf.hash(&mut h);
MailboxHash(h.finish())
};
/* Check if mailbox path (Eg `INBOX/Lists/luddites`) is included in the subscribed /* Check if mailbox path (Eg `INBOX/Lists/luddites`) is included in the subscribed
* mailboxes in user configuration */ * mailboxes in user configuration */
@ -159,7 +162,7 @@ impl MaildirMailbox {
}; };
let ret = MaildirMailbox { let ret = MaildirMailbox {
hash: h.finish(), hash,
name: file_name, name: file_name,
path: fname.unwrap().to_path_buf(), path: fname.unwrap().to_path_buf(),
fs_path: pathbuf, fs_path: pathbuf,

View File

@ -220,7 +220,7 @@ impl MailBackend for MaildirType {
let account_hash = { let account_hash = {
let mut hasher = DefaultHasher::default(); let mut hasher = DefaultHasher::default();
hasher.write(self.name.as_bytes()); hasher.write(self.name.as_bytes());
hasher.finish() AccountHash(hasher.finish())
}; };
let sender = self.event_consumer.clone(); let sender = self.event_consumer.clone();
@ -327,7 +327,7 @@ impl MailBackend for MaildirType {
let account_hash = { let account_hash = {
let mut hasher = DefaultHasher::default(); let mut hasher = DefaultHasher::default();
hasher.write(self.name.as_bytes()); hasher.write(self.name.as_bytes());
hasher.finish() AccountHash(hasher.finish())
}; };
let root_path = self.path.to_path_buf(); let root_path = self.path.to_path_buf();
watcher.watch(&root_path, RecursiveMode::Recursive).unwrap(); watcher.watch(&root_path, RecursiveMode::Recursive).unwrap();
@ -377,7 +377,7 @@ impl MailBackend for MaildirType {
} }
}; };
} }
let mailbox_hash = get_path_hash!(pathbuf); let mailbox_hash = MailboxHash(get_path_hash!(pathbuf));
let file_name = pathbuf let file_name = pathbuf
.as_path() .as_path()
.strip_prefix(&root_path) .strip_prefix(&root_path)
@ -418,7 +418,7 @@ impl MailBackend for MaildirType {
/* Update */ /* Update */
DebouncedEvent::NoticeWrite(pathbuf) | DebouncedEvent::Write(pathbuf) => { DebouncedEvent::NoticeWrite(pathbuf) | DebouncedEvent::Write(pathbuf) => {
debug!("DebouncedEvent::Write(path = {:?}", &pathbuf); debug!("DebouncedEvent::Write(path = {:?}", &pathbuf);
let mailbox_hash = get_path_hash!(pathbuf); let mailbox_hash = MailboxHash(get_path_hash!(pathbuf));
let mut hash_indexes_lock = hash_indexes.lock().unwrap(); let mut hash_indexes_lock = hash_indexes.lock().unwrap();
let index_lock = let index_lock =
&mut hash_indexes_lock.entry(mailbox_hash).or_default(); &mut hash_indexes_lock.entry(mailbox_hash).or_default();
@ -495,7 +495,7 @@ impl MailBackend for MaildirType {
/* Remove */ /* Remove */
DebouncedEvent::NoticeRemove(pathbuf) | DebouncedEvent::Remove(pathbuf) => { DebouncedEvent::NoticeRemove(pathbuf) | DebouncedEvent::Remove(pathbuf) => {
debug!("DebouncedEvent::Remove(path = {:?}", pathbuf); debug!("DebouncedEvent::Remove(path = {:?}", pathbuf);
let mailbox_hash = get_path_hash!(pathbuf); let mailbox_hash = MailboxHash(get_path_hash!(pathbuf));
let mut hash_indexes_lock = hash_indexes.lock().unwrap(); let mut hash_indexes_lock = hash_indexes.lock().unwrap();
let index_lock = hash_indexes_lock.entry(mailbox_hash).or_default(); let index_lock = hash_indexes_lock.entry(mailbox_hash).or_default();
let hash: EnvelopeHash = if let Some((k, _)) = let hash: EnvelopeHash = if let Some((k, _)) =
@ -549,9 +549,9 @@ impl MailBackend for MaildirType {
/* Envelope hasn't changed */ /* Envelope hasn't changed */
DebouncedEvent::Rename(src, dest) => { DebouncedEvent::Rename(src, dest) => {
debug!("DebouncedEvent::Rename(src = {:?}, dest = {:?})", src, dest); debug!("DebouncedEvent::Rename(src = {:?}, dest = {:?})", src, dest);
let mailbox_hash = get_path_hash!(src); let mailbox_hash = MailboxHash(get_path_hash!(src));
let dest_mailbox = { let dest_mailbox = {
let dest_mailbox = get_path_hash!(dest); let dest_mailbox = MailboxHash(get_path_hash!(dest));
if dest_mailbox == mailbox_hash { if dest_mailbox == mailbox_hash {
None None
} else { } else {
@ -787,7 +787,7 @@ impl MailBackend for MaildirType {
/* Maybe a re-read should be triggered here just to be safe. /* Maybe a re-read should be triggered here just to be safe.
(sender)(account_hash, BackendEvent::Refresh(RefreshEvent { (sender)(account_hash, BackendEvent::Refresh(RefreshEvent {
account_hash, account_hash,
mailbox_hash: get_path_hash!(dest), mailbox_hash: MailboxHash(get_path_hash!(dest)),
kind: Rescan, kind: Rescan,
})); }));
*/ */
@ -1011,7 +1011,7 @@ impl MailBackend for MaildirType {
.map(|item| *item.0) .map(|item| *item.0)
}); });
let mailbox_hash = get_path_hash!(&path); let mailbox_hash = MailboxHash(get_path_hash!(&path));
if let Some(parent) = parent { if let Some(parent) = parent {
self.mailboxes self.mailboxes
.entry(parent) .entry(parent)

View File

@ -981,7 +981,7 @@ impl MailBackend for MboxType {
let account_hash = { let account_hash = {
let mut hasher = DefaultHasher::new(); let mut hasher = DefaultHasher::new();
hasher.write(self.account_name.as_bytes()); hasher.write(self.account_name.as_bytes());
hasher.finish() AccountHash(hasher.finish())
}; };
let mailboxes = self.mailboxes.clone(); let mailboxes = self.mailboxes.clone();
let mailbox_index = self.mailbox_index.clone(); let mailbox_index = self.mailbox_index.clone();
@ -1002,7 +1002,7 @@ impl MailBackend for MboxType {
Ok(event) => match event { Ok(event) => match event {
/* Update */ /* Update */
DebouncedEvent::NoticeWrite(pathbuf) | DebouncedEvent::Write(pathbuf) => { DebouncedEvent::NoticeWrite(pathbuf) | DebouncedEvent::Write(pathbuf) => {
let mailbox_hash = get_path_hash!(&pathbuf); let mailbox_hash = MailboxHash(get_path_hash!(&pathbuf));
let file = match std::fs::OpenOptions::new() let file = match std::fs::OpenOptions::new()
.read(true) .read(true)
.write(true) .write(true)
@ -1064,7 +1064,7 @@ impl MailBackend for MboxType {
.values() .values()
.any(|f| f.fs_path == pathbuf) .any(|f| f.fs_path == pathbuf)
{ {
let mailbox_hash = get_path_hash!(&pathbuf); let mailbox_hash = MailboxHash(get_path_hash!(&pathbuf));
(sender)( (sender)(
account_hash, account_hash,
BackendEvent::Refresh(RefreshEvent { BackendEvent::Refresh(RefreshEvent {
@ -1081,7 +1081,7 @@ impl MailBackend for MboxType {
} }
DebouncedEvent::Rename(src, dest) => { DebouncedEvent::Rename(src, dest) => {
if mailboxes.lock().unwrap().values().any(|f| f.fs_path == src) { if mailboxes.lock().unwrap().values().any(|f| f.fs_path == src) {
let mailbox_hash = get_path_hash!(&src); let mailbox_hash = MailboxHash(get_path_hash!(&src));
(sender)( (sender)(
account_hash, account_hash,
BackendEvent::Refresh(RefreshEvent { BackendEvent::Refresh(RefreshEvent {
@ -1265,7 +1265,7 @@ impl MboxType {
.file_name() .file_name()
.map(|f| f.to_string_lossy().into()) .map(|f| f.to_string_lossy().into())
.unwrap_or_default(); .unwrap_or_default();
let hash = get_path_hash!(&ret.path); let hash = MailboxHash(get_path_hash!(&ret.path));
let read_only = if let Ok(metadata) = std::fs::metadata(&ret.path) { let read_only = if let Ok(metadata) = std::fs::metadata(&ret.path) {
metadata.permissions().readonly() metadata.permissions().readonly()
@ -1303,7 +1303,7 @@ impl MboxType {
/* Look for other mailboxes */ /* Look for other mailboxes */
for (k, f) in s.mailboxes.iter() { for (k, f) in s.mailboxes.iter() {
if let Some(path_str) = f.extra.get("path") { if let Some(path_str) = f.extra.get("path") {
let hash = get_path_hash!(path_str); let hash = MailboxHash(get_path_hash!(path_str));
let pathbuf: PathBuf = path_str.into(); let pathbuf: PathBuf = path_str.into();
if !pathbuf.exists() || pathbuf.is_dir() { if !pathbuf.exists() || pathbuf.is_dir() {
return Err(MeliError::new(format!( return Err(MeliError::new(format!(

View File

@ -573,12 +573,12 @@ impl NntpType {
let account_hash = { let account_hash = {
let mut hasher = DefaultHasher::new(); let mut hasher = DefaultHasher::new();
hasher.write(s.name.as_bytes()); hasher.write(s.name.as_bytes());
hasher.finish() AccountHash(hasher.finish())
}; };
let account_name = Arc::new(s.name().to_string()); let account_name = Arc::new(s.name().to_string());
let mut mailboxes = HashMap::default(); let mut mailboxes = HashMap::default();
for (k, _f) in s.mailboxes.iter() { for (k, _f) in s.mailboxes.iter() {
let mailbox_hash = get_path_hash!(&k); let mailbox_hash = MailboxHash(get_path_hash!(&k));
mailboxes.insert( mailboxes.insert(
mailbox_hash, mailbox_hash,
NntpMailbox { NntpMailbox {
@ -645,7 +645,7 @@ impl NntpType {
if s.len() != 3 { if s.len() != 3 {
continue; continue;
} }
let mailbox_hash = get_path_hash!(&s[0]); let mailbox_hash = MailboxHash(get_path_hash!(&s[0]));
mailboxes_lck.entry(mailbox_hash).and_modify(|m| { mailboxes_lck.entry(mailbox_hash).and_modify(|m| {
*m.high_watermark.lock().unwrap() = usize::from_str(s[1]).unwrap_or(0); *m.high_watermark.lock().unwrap() = usize::from_str(s[1]).unwrap_or(0);
*m.low_watermark.lock().unwrap() = usize::from_str(s[2]).unwrap_or(0); *m.low_watermark.lock().unwrap() = usize::from_str(s[2]).unwrap_or(0);

View File

@ -367,7 +367,7 @@ impl NotmuchDb {
let hash = { let hash = {
let mut h = DefaultHasher::new(); let mut h = DefaultHasher::new();
k.hash(&mut h); k.hash(&mut h);
h.finish() MailboxHash(h.finish())
}; };
mailboxes.insert( mailboxes.insert(
hash, hash,
@ -396,7 +396,7 @@ impl NotmuchDb {
let account_hash = { let account_hash = {
let mut hasher = DefaultHasher::new(); let mut hasher = DefaultHasher::new();
hasher.write(s.name().as_bytes()); hasher.write(s.name().as_bytes());
hasher.finish() AccountHash(hasher.finish())
}; };
Ok(Box::new(NotmuchDb { Ok(Box::new(NotmuchDb {
lib, lib,
@ -533,7 +533,7 @@ impl MailBackend for NotmuchDb {
database: Arc<DbConnection>, database: Arc<DbConnection>,
index: Arc<RwLock<HashMap<EnvelopeHash, CString>>>, index: Arc<RwLock<HashMap<EnvelopeHash, CString>>>,
mailbox_index: Arc<RwLock<HashMap<EnvelopeHash, SmallVec<[MailboxHash; 16]>>>>, mailbox_index: Arc<RwLock<HashMap<EnvelopeHash, SmallVec<[MailboxHash; 16]>>>>,
mailboxes: Arc<RwLock<HashMap<u64, NotmuchMailbox>>>, mailboxes: Arc<RwLock<HashMap<MailboxHash, NotmuchMailbox>>>,
tag_index: Arc<RwLock<BTreeMap<u64, String>>>, tag_index: Arc<RwLock<BTreeMap<u64, String>>>,
iter: std::vec::IntoIter<CString>, iter: std::vec::IntoIter<CString>,
} }

View File

@ -195,6 +195,8 @@ pub struct SmtpExtensionSupport {
pipelining: bool, pipelining: bool,
#[serde(default = "crate::conf::true_val")] #[serde(default = "crate::conf::true_val")]
chunking: bool, chunking: bool,
#[serde(default = "crate::conf::true_val")]
_8bitmime: bool,
//Essentially, the PRDR extension to SMTP allows (but does not require) an SMTP server to //Essentially, the PRDR extension to SMTP allows (but does not require) an SMTP server to
//issue multiple responses after a message has been transferred, by mutual consent of the //issue multiple responses after a message has been transferred, by mutual consent of the
//client and server. SMTP clients that support the PRDR extension then use the expanded //client and server. SMTP clients that support the PRDR extension then use the expanded
@ -202,7 +204,7 @@ pub struct SmtpExtensionSupport {
//envelope exchange. //envelope exchange.
#[serde(default = "crate::conf::true_val")] #[serde(default = "crate::conf::true_val")]
prdr: bool, prdr: bool,
#[serde(default = "crate::conf::false_val")] #[serde(default = "crate::conf::true_val")]
binarymime: bool, binarymime: bool,
//Resources: //Resources:
//- http://www.postfix.org/SMTPUTF8_README.html //- http://www.postfix.org/SMTPUTF8_README.html
@ -224,7 +226,8 @@ impl Default for SmtpExtensionSupport {
pipelining: true, pipelining: true,
chunking: true, chunking: true,
prdr: true, prdr: true,
binarymime: false, _8bitmime: true,
binarymime: true,
smtputf8: true, smtputf8: true,
auth: true, auth: true,
dsn_notify: Some("FAILURE".into()), dsn_notify: Some("FAILURE".into()),
@ -561,6 +564,7 @@ impl SmtpConnection {
self.server_conf.extensions.pipelining &= reply.lines.contains(&"PIPELINING"); self.server_conf.extensions.pipelining &= reply.lines.contains(&"PIPELINING");
self.server_conf.extensions.chunking &= reply.lines.contains(&"CHUNKING"); self.server_conf.extensions.chunking &= reply.lines.contains(&"CHUNKING");
self.server_conf.extensions.prdr &= reply.lines.contains(&"PRDR"); self.server_conf.extensions.prdr &= reply.lines.contains(&"PRDR");
self.server_conf.extensions._8bitmime &= reply.lines.contains(&"8BITMIME");
self.server_conf.extensions.binarymime &= reply.lines.contains(&"BINARYMIME"); self.server_conf.extensions.binarymime &= reply.lines.contains(&"BINARYMIME");
self.server_conf.extensions.smtputf8 &= reply.lines.contains(&"SMTPUTF8"); self.server_conf.extensions.smtputf8 &= reply.lines.contains(&"SMTPUTF8");
if !reply.lines.contains(&"DSN") { if !reply.lines.contains(&"DSN") {
@ -637,6 +641,11 @@ impl SmtpConnection {
if self.server_conf.extensions.prdr { if self.server_conf.extensions.prdr {
current_command.push(b" PRDR"); current_command.push(b" PRDR");
} }
if self.server_conf.extensions.binarymime {
current_command.push(b" BODY=BINARYMIME");
} else if self.server_conf.extensions._8bitmime {
current_command.push(b" BODY=8BITMIME");
}
self.send_command(&current_command).await?; self.send_command(&current_command).await?;
current_command.clear(); current_command.clear();
if !self.server_conf.extensions.pipelining { if !self.server_conf.extensions.pipelining {
@ -681,71 +690,81 @@ impl SmtpConnection {
//permitted on either side of the colon following FROM in the MAIL command or TO in the //permitted on either side of the colon following FROM in the MAIL command or TO in the
//RCPT command. The syntax is exactly as given above. //RCPT command. The syntax is exactly as given above.
//The third step in the procedure is the DATA command if self.server_conf.extensions.binarymime {
//(or some alternative specified in a service extension). let mail_length = format!("{}", mail.as_bytes().len());
//DATA <CRLF> self.send_command(&[b"BDAT", mail_length.as_bytes(), b"LAST"])
self.send_command(&[b"DATA"]).await?; .await?;
//Client SMTP implementations that employ pipelining MUST check ALL statuses associated self.stream
//with each command in a group. For example, if none of the RCPT TO recipient addresses .write_all(mail.as_bytes())
//were accepted the client must then check the response to the DATA command -- the client .await
//cannot assume that the DATA command will be rejected just because none of the RCPT TO .chain_err_kind(crate::error::ErrorKind::Network)?;
//commands worked. If the DATA command was properly rejected the client SMTP can just } else {
//issue RSET, but if the DATA command was accepted the client SMTP should send a single //The third step in the procedure is the DATA command
//dot. //(or some alternative specified in a service extension).
let mut _all_error = self.server_conf.extensions.pipelining; //DATA <CRLF>
let mut _any_error = false; self.send_command(&[b"DATA"]).await?;
let mut ignore_mailfrom = true; //Client SMTP implementations that employ pipelining MUST check ALL statuses associated
for expected_reply_code in pipelining_queue { //with each command in a group. For example, if none of the RCPT TO recipient addresses
let reply = self.read_lines(&mut res, expected_reply_code).await?; //were accepted the client must then check the response to the DATA command -- the client
if !ignore_mailfrom { //cannot assume that the DATA command will be rejected just because none of the RCPT TO
_all_error &= reply.code.is_err(); //commands worked. If the DATA command was properly rejected the client SMTP can just
_any_error |= reply.code.is_err(); //issue RSET, but if the DATA command was accepted the client SMTP should send a single
//dot.
let mut _all_error = self.server_conf.extensions.pipelining;
let mut _any_error = false;
let mut ignore_mailfrom = true;
for expected_reply_code in pipelining_queue {
let reply = self.read_lines(&mut res, expected_reply_code).await?;
if !ignore_mailfrom {
_all_error &= reply.code.is_err();
_any_error |= reply.code.is_err();
}
ignore_mailfrom = false;
pipelining_results.push(reply.into());
} }
ignore_mailfrom = false;
pipelining_results.push(reply.into());
}
//If accepted, the SMTP server returns a 354 Intermediate reply and considers all //If accepted, the SMTP server returns a 354 Intermediate reply and considers all
//succeeding lines up to but not including the end of mail data indicator to be the //succeeding lines up to but not including the end of mail data indicator to be the
//message text. When the end of text is successfully received and stored, the //message text. When the end of text is successfully received and stored, the
//SMTP-receiver sends a "250 OK" reply. //SMTP-receiver sends a "250 OK" reply.
self.read_lines(&mut res, Some((ReplyCode::_354, &[]))) self.read_lines(&mut res, Some((ReplyCode::_354, &[])))
.await?; .await?;
//Before sending a line of mail text, the SMTP client checks the first character of the //Before sending a line of mail text, the SMTP client checks the first character of the
//line.If it is a period, one additional period is inserted at the beginning of the line. //line.If it is a period, one additional period is inserted at the beginning of the line.
for line in mail.lines() { for line in mail.lines() {
if line.starts_with('.') { if line.starts_with('.') {
self.stream
.write_all(b".")
.await
.chain_err_kind(crate::error::ErrorKind::Network)?;
}
self.stream self.stream
.write_all(b".") .write_all(line.as_bytes())
.await
.chain_err_kind(crate::error::ErrorKind::Network)?;
self.stream
.write_all(b"\r\n")
.await .await
.chain_err_kind(crate::error::ErrorKind::Network)?; .chain_err_kind(crate::error::ErrorKind::Network)?;
} }
self.stream
.write_all(line.as_bytes())
.await
.chain_err_kind(crate::error::ErrorKind::Network)?;
self.stream
.write_all(b"\r\n")
.await
.chain_err_kind(crate::error::ErrorKind::Network)?;
}
if !mail.ends_with('\n') { if !mail.ends_with('\n') {
self.stream
.write_all(b".\r\n")
.await
.chain_err_kind(crate::error::ErrorKind::Network)?;
}
//The mail data are terminated by a line containing only a period, that is, the character
//sequence "<CRLF>.<CRLF>", where the first <CRLF> is actually the terminator of the
//previous line (see Section 4.5.2). This is the end of mail data indication.
self.stream self.stream
.write_all(b".\r\n") .write_all(b".\r\n")
.await .await
.chain_err_kind(crate::error::ErrorKind::Network)?; .chain_err_kind(crate::error::ErrorKind::Network)?;
} }
//The mail data are terminated by a line containing only a period, that is, the character
//sequence "<CRLF>.<CRLF>", where the first <CRLF> is actually the terminator of the
//previous line (see Section 4.5.2). This is the end of mail data indication.
self.stream
.write_all(b".\r\n")
.await
.chain_err_kind(crate::error::ErrorKind::Network)?;
//The end of mail data indicator also confirms the mail transaction and tells the SMTP //The end of mail data indicator also confirms the mail transaction and tells the SMTP
//server to now process the stored recipients and mail data. If accepted, the SMTP //server to now process the stored recipients and mail data. If accepted, the SMTP
//server returns a "250 OK" reply. //server returns a "250 OK" reply.

View File

@ -151,7 +151,7 @@ impl Composer {
pager.set_show_scrollbar(true); pager.set_show_scrollbar(true);
Composer { Composer {
reply_context: None, reply_context: None,
account_hash: 0, account_hash: AccountHash(0),
cursor: Cursor::Headers, cursor: Cursor::Headers,
pager, pager,
draft: Draft::default(), draft: Draft::default(),

View File

@ -613,6 +613,7 @@ pub struct Listing {
cursor_pos: (usize, MenuEntryCursor), cursor_pos: (usize, MenuEntryCursor),
menu_cursor_pos: (usize, MenuEntryCursor), menu_cursor_pos: (usize, MenuEntryCursor),
menu_content: CellBuffer, menu_content: CellBuffer,
menu_content_dirty: bool,
menu_scrollbar_show_timer: crate::jobs::Timer, menu_scrollbar_show_timer: crate::jobs::Timer,
show_menu_scrollbar: ShowMenuScrollbar, show_menu_scrollbar: ShowMenuScrollbar,
startup_checks_rate: RateLimit, startup_checks_rate: RateLimit,
@ -698,7 +699,8 @@ impl Component for Listing {
match self.component { match self.component {
ListingComponent::Offline(_) => {} ListingComponent::Offline(_) => {}
_ => { _ => {
self.component = Offline(OfflineListing::new((account_hash, 0))); self.component =
Offline(OfflineListing::new((account_hash, MailboxHash(0))));
} }
} }
} }
@ -720,7 +722,8 @@ impl Component for Listing {
match self.component { match self.component {
ListingComponent::Offline(_) => {} ListingComponent::Offline(_) => {}
_ => { _ => {
self.component = Offline(OfflineListing::new((account_hash, 0))); self.component =
Offline(OfflineListing::new((account_hash, MailboxHash(0))));
} }
} }
} }
@ -743,6 +746,7 @@ impl Component for Listing {
*account_settings!(context[account_hash].listing.sidebar_divider); *account_settings!(context[account_hash].listing.sidebar_divider);
self.sidebar_divider_theme = conf::value(context, "mail.sidebar_divider"); self.sidebar_divider_theme = conf::value(context, "mail.sidebar_divider");
self.menu_content = CellBuffer::new_with_context(0, 0, None, context); self.menu_content = CellBuffer::new_with_context(0, 0, None, context);
self.menu_content_dirty = true;
self.set_dirty(true); self.set_dirty(true);
} }
UIEvent::Timer(n) if *n == self.menu_scrollbar_show_timer.id() => { UIEvent::Timer(n) if *n == self.menu_scrollbar_show_timer.id() => {
@ -750,6 +754,7 @@ impl Component for Listing {
self.show_menu_scrollbar = ShowMenuScrollbar::False; self.show_menu_scrollbar = ShowMenuScrollbar::False;
self.set_dirty(true); self.set_dirty(true);
self.menu_content.empty(); self.menu_content.empty();
self.menu_content_dirty = true;
} }
return true; return true;
} }
@ -775,41 +780,10 @@ impl Component for Listing {
if self.cursor_pos.0 == account_index { if self.cursor_pos.0 == account_index {
self.change_account(context); self.change_account(context);
} else { } else {
let previous_collapsed_mailboxes: BTreeSet<MailboxHash> = self.accounts self.update_menu_account(account_index, context);
[account_index]
.entries
.iter()
.filter_map(|e| {
if e.collapsed {
Some(e.mailbox_hash)
} else {
None
}
})
.collect::<_>();
self.accounts[account_index].entries = context.accounts[&*account_hash]
.list_mailboxes()
.into_iter()
.filter(|mailbox_node| {
context.accounts[&*account_hash][&mailbox_node.hash]
.ref_mailbox
.is_subscribed()
})
.map(|f| MailboxMenuEntry {
depth: f.depth,
indentation: f.indentation,
has_sibling: f.has_sibling,
mailbox_hash: f.hash,
visible: true,
collapsed: if previous_collapsed_mailboxes.is_empty() {
context.accounts[&*account_hash][&f.hash].conf.collapsed
} else {
previous_collapsed_mailboxes.contains(&f.hash)
},
})
.collect::<_>();
self.set_dirty(true); self.set_dirty(true);
self.menu_content.empty(); self.menu_content.empty();
self.menu_content_dirty = true;
context context
.replies .replies
.push_back(UIEvent::StatusEvent(StatusEvent::UpdateStatus( .push_back(UIEvent::StatusEvent(StatusEvent::UpdateStatus(
@ -825,35 +799,8 @@ impl Component for Listing {
.get_index_of(account_hash) .get_index_of(account_hash)
.expect("Invalid account_hash in UIEventMailbox{Delete,Create}"); .expect("Invalid account_hash in UIEventMailbox{Delete,Create}");
self.menu_content.empty(); self.menu_content.empty();
let previous_collapsed_mailboxes: BTreeSet<MailboxHash> = self.accounts self.menu_content_dirty = true;
[account_index] self.update_menu_account(account_index, context);
.entries
.iter()
.filter_map(|e| {
if e.collapsed {
Some(e.mailbox_hash)
} else {
None
}
})
.collect::<_>();
self.accounts[account_index].entries = context.accounts[&*account_hash]
.list_mailboxes()
.into_iter()
.filter(|mailbox_node| {
context.accounts[&*account_hash][&mailbox_node.hash]
.ref_mailbox
.is_subscribed()
})
.map(|f| MailboxMenuEntry {
depth: f.depth,
indentation: f.indentation,
has_sibling: f.has_sibling,
mailbox_hash: f.hash,
visible: true,
collapsed: previous_collapsed_mailboxes.contains(&f.hash),
})
.collect::<_>();
let mut fallback = 0; let mut fallback = 0;
if let MenuEntryCursor::Mailbox(ref mut cur) = self.cursor_pos.1 { if let MenuEntryCursor::Mailbox(ref mut cur) = self.cursor_pos.1 {
*cur = std::cmp::min( *cur = std::cmp::min(
@ -900,6 +847,7 @@ impl Component for Listing {
self.component self.component
.set_coordinates((account_hash, *mailbox_hash)); .set_coordinates((account_hash, *mailbox_hash));
self.menu_content.empty(); self.menu_content.empty();
self.menu_content_dirty = true;
self.set_dirty(true); self.set_dirty(true);
} }
return true; return true;
@ -1378,6 +1326,7 @@ impl Component for Listing {
target.collapsed = !(target.collapsed); target.collapsed = !(target.collapsed);
self.dirty = true; self.dirty = true;
self.menu_content.empty(); self.menu_content.empty();
self.menu_content_dirty = true;
context context
.replies .replies
.push_back(UIEvent::StatusEvent(StatusEvent::ScrollUpdate( .push_back(UIEvent::StatusEvent(StatusEvent::ScrollUpdate(
@ -1520,6 +1469,7 @@ impl Component for Listing {
self.show_menu_scrollbar = ShowMenuScrollbar::True; self.show_menu_scrollbar = ShowMenuScrollbar::True;
} }
self.menu_content.empty(); self.menu_content.empty();
self.menu_content_dirty = true;
self.set_dirty(true); self.set_dirty(true);
return true; return true;
} }
@ -1581,6 +1531,7 @@ impl Component for Listing {
self.show_menu_scrollbar = ShowMenuScrollbar::True; self.show_menu_scrollbar = ShowMenuScrollbar::True;
} }
self.menu_content.empty(); self.menu_content.empty();
self.menu_content_dirty = true;
return true; return true;
} }
UIEvent::Input(ref k) UIEvent::Input(ref k)
@ -1634,6 +1585,7 @@ impl Component for Listing {
self.show_menu_scrollbar = ShowMenuScrollbar::True; self.show_menu_scrollbar = ShowMenuScrollbar::True;
} }
self.menu_content.empty(); self.menu_content.empty();
self.menu_content_dirty = true;
self.set_dirty(true); self.set_dirty(true);
return true; return true;
@ -1660,6 +1612,7 @@ impl Component for Listing {
self.dirty = true; self.dirty = true;
/* clear menu to force redraw */ /* clear menu to force redraw */
self.menu_content.empty(); self.menu_content.empty();
self.menu_content_dirty = true;
context context
.replies .replies
.push_back(UIEvent::StatusEvent(StatusEvent::UpdateStatus( .push_back(UIEvent::StatusEvent(StatusEvent::UpdateStatus(
@ -1810,13 +1763,14 @@ impl Listing {
.collect(); .collect();
let first_account_hash = account_entries[0].hash; let first_account_hash = account_entries[0].hash;
let mut ret = Listing { let mut ret = Listing {
component: Offline(OfflineListing::new((first_account_hash, 0))), component: Offline(OfflineListing::new((first_account_hash, MailboxHash(0)))),
accounts: account_entries, accounts: account_entries,
status: None, status: None,
dirty: true, dirty: true,
cursor_pos: (0, MenuEntryCursor::Mailbox(0)), cursor_pos: (0, MenuEntryCursor::Mailbox(0)),
menu_cursor_pos: (0, MenuEntryCursor::Mailbox(0)), menu_cursor_pos: (0, MenuEntryCursor::Mailbox(0)),
menu_content: CellBuffer::new_with_context(0, 0, None, context), menu_content: CellBuffer::new_with_context(0, 0, None, context),
menu_content_dirty: true,
menu_scrollbar_show_timer: context.job_executor.clone().create_timer( menu_scrollbar_show_timer: context.job_executor.clone().create_timer(
std::time::Duration::from_secs(0), std::time::Duration::from_secs(0),
std::time::Duration::from_millis(1200), std::time::Duration::from_millis(1200),
@ -1841,6 +1795,11 @@ impl Listing {
} }
fn draw_menu(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { fn draw_menu(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
if self.menu_content_dirty {
for account_index in 0..self.accounts.len() {
self.update_menu_account(account_index, context);
}
}
clear_area(grid, area, self.theme_default); clear_area(grid, area, self.theme_default);
let total_height: usize = 3 * (self.accounts.len()) let total_height: usize = 3 * (self.accounts.len())
+ self + self
@ -2283,9 +2242,8 @@ impl Listing {
} }
} }
fn change_account(&mut self, context: &mut Context) { fn update_menu_account(&mut self, account_index: usize, context: &mut Context) {
let account_hash = context.accounts[self.cursor_pos.0].hash(); let previous_collapsed_mailboxes: BTreeSet<MailboxHash> = self.accounts[account_index]
let previous_collapsed_mailboxes: BTreeSet<MailboxHash> = self.accounts[self.cursor_pos.0]
.entries .entries
.iter() .iter()
.filter_map(|e| { .filter_map(|e| {
@ -2296,11 +2254,11 @@ impl Listing {
} }
}) })
.collect::<_>(); .collect::<_>();
self.accounts[self.cursor_pos.0].entries = context.accounts[self.cursor_pos.0] self.accounts[account_index].entries = context.accounts[account_index]
.list_mailboxes() .list_mailboxes()
.into_iter() .into_iter()
.filter(|mailbox_node| { .filter(|mailbox_node| {
context.accounts[self.cursor_pos.0][&mailbox_node.hash] context.accounts[account_index][&mailbox_node.hash]
.ref_mailbox .ref_mailbox
.is_subscribed() .is_subscribed()
}) })
@ -2311,12 +2269,17 @@ impl Listing {
mailbox_hash: f.hash, mailbox_hash: f.hash,
visible: true, visible: true,
collapsed: if previous_collapsed_mailboxes.is_empty() { collapsed: if previous_collapsed_mailboxes.is_empty() {
context.accounts[self.cursor_pos.0][&f.hash].conf.collapsed context.accounts[account_index][&f.hash].conf.collapsed
} else { } else {
previous_collapsed_mailboxes.contains(&f.hash) previous_collapsed_mailboxes.contains(&f.hash)
}, },
}) })
.collect::<_>(); .collect::<_>();
}
fn change_account(&mut self, context: &mut Context) {
self.update_menu_account(self.cursor_pos.0, context);
let account_hash = self.accounts[self.cursor_pos.0].hash;
match self.cursor_pos.1 { match self.cursor_pos.1 {
MenuEntryCursor::Mailbox(idx) => { MenuEntryCursor::Mailbox(idx) => {
/* Account might have no mailboxes yet if it's offline */ /* Account might have no mailboxes yet if it's offline */
@ -2334,7 +2297,7 @@ impl Listing {
self.component.set_style(*index_style); self.component.set_style(*index_style);
} else { } else {
/* Set to dummy */ /* Set to dummy */
self.component = Offline(OfflineListing::new((account_hash, 0))); self.component = Offline(OfflineListing::new((account_hash, MailboxHash(0))));
} }
self.status = None; self.status = None;
context context

View File

@ -849,7 +849,7 @@ impl CompactListing {
pub const DESCRIPTION: &'static str = "compact listing"; pub const DESCRIPTION: &'static str = "compact listing";
pub fn new(coordinates: (AccountHash, MailboxHash)) -> Box<Self> { pub fn new(coordinates: (AccountHash, MailboxHash)) -> Box<Self> {
Box::new(CompactListing { Box::new(CompactListing {
cursor_pos: (coordinates.0, 1, 0), cursor_pos: (coordinates.0, MailboxHash(0), 0),
new_cursor_pos: (coordinates.0, coordinates.1, 0), new_cursor_pos: (coordinates.0, coordinates.1, 0),
length: 0, length: 0,
sort: (Default::default(), Default::default()), sort: (Default::default(), Default::default()),

View File

@ -570,7 +570,7 @@ impl ConversationsListing {
pub fn new(coordinates: (AccountHash, MailboxHash)) -> Box<Self> { pub fn new(coordinates: (AccountHash, MailboxHash)) -> Box<Self> {
Box::new(ConversationsListing { Box::new(ConversationsListing {
cursor_pos: (coordinates.0, 1, 0), cursor_pos: (coordinates.0, MailboxHash(0), 0),
new_cursor_pos: (coordinates.0, coordinates.1, 0), new_cursor_pos: (coordinates.0, coordinates.1, 0),
length: 0, length: 0,
sort: (Default::default(), Default::default()), sort: (Default::default(), Default::default()),

View File

@ -660,7 +660,7 @@ impl PlainListing {
const DESCRIPTION: &'static str = "plain listing"; const DESCRIPTION: &'static str = "plain listing";
pub fn new(coordinates: (AccountHash, MailboxHash)) -> Box<Self> { pub fn new(coordinates: (AccountHash, MailboxHash)) -> Box<Self> {
Box::new(PlainListing { Box::new(PlainListing {
cursor_pos: (0, 1, 0), cursor_pos: (AccountHash(0), MailboxHash(0), 0),
new_cursor_pos: (coordinates.0, coordinates.1, 0), new_cursor_pos: (coordinates.0, coordinates.1, 0),
length: 0, length: 0,
sort: (Default::default(), Default::default()), sort: (Default::default(), Default::default()),

View File

@ -759,7 +759,7 @@ impl fmt::Display for ThreadListing {
impl ThreadListing { impl ThreadListing {
pub fn new(coordinates: (AccountHash, MailboxHash)) -> Box<Self> { pub fn new(coordinates: (AccountHash, MailboxHash)) -> Box<Self> {
Box::new(ThreadListing { Box::new(ThreadListing {
cursor_pos: (coordinates.0, 0, 0), cursor_pos: (coordinates.0, MailboxHash(0), 0),
new_cursor_pos: (coordinates.0, coordinates.1, 0), new_cursor_pos: (coordinates.0, coordinates.1, 0),
length: 0, length: 0,
sort: (Default::default(), Default::default()), sort: (Default::default(), Default::default()),

View File

@ -1652,7 +1652,7 @@ impl Component for MailView {
} }
fn process_event(&mut self, mut event: &mut UIEvent, context: &mut Context) -> bool { fn process_event(&mut self, mut event: &mut UIEvent, context: &mut Context) -> bool {
if self.coordinates.0 == 0 || self.coordinates.1 == 0 { if self.coordinates.0 .0 == 0 || self.coordinates.1 .0 == 0 {
return false; return false;
} }
let shortcuts = self.get_shortcuts(context); let shortcuts = self.get_shortcuts(context);

View File

@ -1112,7 +1112,7 @@ impl Account {
} }
pub fn load(&mut self, mailbox_hash: MailboxHash) -> result::Result<(), usize> { pub fn load(&mut self, mailbox_hash: MailboxHash) -> result::Result<(), usize> {
if mailbox_hash == 0 { if mailbox_hash.0 == 0 {
return Err(0); return Err(0);
} }
match self.mailbox_entries[&mailbox_hash].status { match self.mailbox_entries[&mailbox_hash].status {

View File

@ -314,7 +314,12 @@ fn run_app(opt: Opt) -> Result<()> {
sender, sender,
receiver.clone(), receiver.clone(),
)?; )?;
state.register_component(Box::new(EnvelopeView::new(wrapper, None, None, 0))); state.register_component(Box::new(EnvelopeView::new(
wrapper,
None,
None,
AccountHash(0),
)));
} else { } else {
state = State::new(None, sender, receiver.clone())?; state = State::new(None, sender, receiver.clone())?;
#[cfg(feature = "svgscreenshot")] #[cfg(feature = "svgscreenshot")]

View File

@ -258,7 +258,7 @@ impl State {
use std::hash::Hasher; use std::hash::Hasher;
let mut hasher = DefaultHasher::new(); let mut hasher = DefaultHasher::new();
hasher.write(n.as_bytes()); hasher.write(n.as_bytes());
hasher.finish() AccountHash(hasher.finish())
}; };
Account::new( Account::new(
account_hash, account_hash,