Compare commits
4 commits
master
...
imap-lemon
Author | SHA1 | Date | |
---|---|---|---|
![]() |
b2ff359eb7 | ||
![]() |
9fcf88b494 | ||
![]() |
808bdf75a1 | ||
![]() |
ed16e29de1 |
21 changed files with 213 additions and 184 deletions
|
@ -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 {
|
||||||
|
|
|
@ -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?;
|
||||||
|
|
|
@ -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<_, _>>()?;
|
||||||
|
|
|
@ -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(())
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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 hash = {
|
||||||
let mut h = DefaultHasher::new();
|
let mut h = DefaultHasher::new();
|
||||||
pathbuf.hash(&mut h);
|
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,
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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!(
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(¤t_command).await?;
|
self.send_command(¤t_command).await?;
|
||||||
current_command.clear();
|
current_command.clear();
|
||||||
if !self.server_conf.extensions.pipelining {
|
if !self.server_conf.extensions.pipelining {
|
||||||
|
@ -681,6 +690,15 @@ 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.
|
||||||
|
|
||||||
|
if self.server_conf.extensions.binarymime {
|
||||||
|
let mail_length = format!("{}", mail.as_bytes().len());
|
||||||
|
self.send_command(&[b"BDAT", mail_length.as_bytes(), b"LAST"])
|
||||||
|
.await?;
|
||||||
|
self.stream
|
||||||
|
.write_all(mail.as_bytes())
|
||||||
|
.await
|
||||||
|
.chain_err_kind(crate::error::ErrorKind::Network)?;
|
||||||
|
} else {
|
||||||
//The third step in the procedure is the DATA command
|
//The third step in the procedure is the DATA command
|
||||||
//(or some alternative specified in a service extension).
|
//(or some alternative specified in a service extension).
|
||||||
//DATA <CRLF>
|
//DATA <CRLF>
|
||||||
|
@ -745,6 +763,7 @@ impl SmtpConnection {
|
||||||
.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 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
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()),
|
||||||
|
|
|
@ -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()),
|
||||||
|
|
|
@ -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()),
|
||||||
|
|
|
@ -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()),
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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")]
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Add table
Reference in a new issue