Browse Source

melib: Add BackendFolder methods, move special usage logic to backend

- add count() method to return (unseen, total) counts
- add is_subscribed()
- add set_special_usage() and set_is_subscribed()

concerns #8
async
Manos Pitsidianakis 2 years ago
parent
commit
2b6f6ab42c
Signed by: epilys GPG Key ID: 73627C2F690DF710
  1. 36
      melib/src/backends.rs
  2. 30
      melib/src/backends/imap.rs
  3. 37
      melib/src/backends/imap/connection.rs
  4. 38
      melib/src/backends/imap/folder.rs
  5. 6
      melib/src/backends/imap/protocol_parser.rs
  6. 17
      melib/src/backends/imap/watch.rs
  7. 5
      melib/src/backends/jmap.rs
  8. 27
      melib/src/backends/jmap/connection.rs
  9. 27
      melib/src/backends/jmap/folder.rs
  10. 2
      melib/src/backends/jmap/operations.rs
  11. 8
      melib/src/backends/jmap/protocol.rs
  12. 43
      melib/src/backends/maildir.rs
  13. 31
      melib/src/backends/maildir/backend.rs
  14. 31
      melib/src/backends/mbox.rs
  15. 30
      melib/src/backends/notmuch.rs
  16. 28
      ui/src/components/mail/listing.rs
  17. 44
      ui/src/components/mail/status.rs
  18. 51
      ui/src/conf/accounts.rs

36
melib/src/backends.rs

@ -372,6 +372,26 @@ pub enum SpecialUsageMailbox {
Trash,
}
impl std::fmt::Display for SpecialUsageMailbox {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
use SpecialUsageMailbox::*;
write!(
f,
"{}",
match self {
Normal => "Normal",
Inbox => "Inbox",
Archive => "Archive",
Drafts => "Drafts",
Flagged => "Flagged",
Junk => "Junk",
Sent => "Sent",
Trash => "Trash",
}
)
}
}
impl Default for SpecialUsageMailbox {
fn default() -> Self {
SpecialUsageMailbox::Normal
@ -409,8 +429,12 @@ pub trait BackendFolder: Debug {
fn clone(&self) -> Folder;
fn children(&self) -> &[FolderHash];
fn parent(&self) -> Option<FolderHash>;
fn is_subscribed(&self) -> bool;
fn set_is_subscribed(&mut self, new_val: bool) -> Result<()>;
fn set_special_usage(&mut self, new_val: SpecialUsageMailbox) -> Result<()>;
fn special_usage(&self) -> SpecialUsageMailbox;
fn permissions(&self) -> FolderPermissions;
fn count(&self) -> Result<(usize, usize)>;
}
#[derive(Debug)]
@ -452,6 +476,18 @@ impl BackendFolder for DummyFolder {
fn permissions(&self) -> FolderPermissions {
FolderPermissions::default()
}
fn is_subscribed(&self) -> bool {
true
}
fn set_is_subscribed(&mut self, _new_val: bool) -> Result<()> {
Ok(())
}
fn set_special_usage(&mut self, _new_val: SpecialUsageMailbox) -> Result<()> {
Ok(())
}
fn count(&self) -> Result<(usize, usize)> {
Ok((0, 0))
}
}
pub fn folder_default() -> Folder {

30
melib/src/backends/imap.rs

@ -169,9 +169,14 @@ impl MailBackend for ImapType {
let can_create_flags = self.can_create_flags.clone();
let folder_path = folder.path().to_string();
let folder_hash = folder.hash();
let (permissions, folder_exists, no_select) = {
let (permissions, folder_exists, no_select, unseen) = {
let f = &self.folders.read().unwrap()[&folder_hash];
(f.permissions.clone(), f.exists.clone(), f.no_select)
(
f.permissions.clone(),
f.exists.clone(),
f.no_select,
f.unseen.clone(),
)
};
let connection = self.connection.clone();
let closure = move |_work_context| {
@ -228,10 +233,11 @@ impl MailBackend for ImapType {
);
let mut tag_lck = tag_index.write().unwrap();
let mut our_unseen = 0;
while exists > 1 {
let mut envelopes = vec![];
exit_on_error!(&tx,
conn.send_command(format!("UID FETCH {}:{} (UID FLAGS ENVELOPE BODYSTRUCTURE)", std::cmp::max(exists.saturating_sub(20000), 1), exists).as_bytes())
conn.send_command(format!("UID FETCH {}:{} (UID FLAGS ENVELOPE BODYSTRUCTURE)", std::cmp::max(exists.saturating_sub(500), 1), exists).as_bytes())
conn.read_response(&mut response)
);
debug!(
@ -255,6 +261,9 @@ impl MailBackend for ImapType {
h.write(folder_path.as_bytes());
env.set_hash(h.finish());
if let Some((flags, keywords)) = flags {
if !flags.contains(Flag::SEEN) {
our_unseen += 1;
}
env.set_flags(flags);
for f in keywords {
let hash = tag_hash!(f);
@ -278,8 +287,10 @@ impl MailBackend for ImapType {
tx.send(AsyncStatus::Payload(Err(e))).unwrap();
}
}
exists = std::cmp::max(exists.saturating_sub(20000), 1);
exists = std::cmp::max(exists.saturating_sub(500), 1);
debug!("sending payload");
*unseen.lock().unwrap() = our_unseen;
tx.send(AsyncStatus::Payload(Ok(envelopes))).unwrap();
}
drop(conn);
@ -290,6 +301,17 @@ impl MailBackend for ImapType {
w.build(handle)
}
fn refresh(
&mut self,
_folder_hash: FolderHash,
_sender: RefreshEventConsumer,
) -> Result<Async<Result<Vec<RefreshEvent>>>> {
let mut res = String::with_capacity(8 * 1024);
self.connection.lock()?.send_command(b"NOOP")?;
self.connection.lock()?.read_response(&mut res)?;
Err(MeliError::new("Unimplemented."))
}
fn watch(
&self,
sender: RefreshEventConsumer,

37
melib/src/backends/imap/connection.rs

@ -357,6 +357,13 @@ impl ImapConnection {
}
pub fn read_lines(&mut self, ret: &mut String, termination_string: String) -> Result<()> {
if let (instant, ref mut status @ Ok(())) = *self.online.lock().unwrap() {
if Instant::now().duration_since(instant) >= std::time::Duration::new(60 * 30, 0) {
*status = Err(MeliError::new("Connection timed out"));
self.stream = Err(MeliError::new("Connection timed out"));
}
}
if let Ok(ref mut stream) = self.stream {
if let Ok(_) = stream.read_lines(ret, &termination_string) {
return Ok(());
@ -381,6 +388,12 @@ impl ImapConnection {
}
pub fn wait_for_continuation_request(&mut self) -> Result<()> {
if let (instant, ref mut status @ Ok(())) = *self.online.lock().unwrap() {
if Instant::now().duration_since(instant) >= std::time::Duration::new(60 * 30, 0) {
*status = Err(MeliError::new("Connection timed out"));
self.stream = Err(MeliError::new("Connection timed out"));
}
}
if let Ok(ref mut stream) = self.stream {
if let Ok(_) = stream.wait_for_continuation_request() {
return Ok(());
@ -405,6 +418,12 @@ impl ImapConnection {
}
pub fn send_command(&mut self, command: &[u8]) -> Result<usize> {
if let (instant, ref mut status @ Ok(())) = *self.online.lock().unwrap() {
if Instant::now().duration_since(instant) >= std::time::Duration::new(60 * 30, 0) {
*status = Err(MeliError::new("Connection timed out"));
self.stream = Err(MeliError::new("Connection timed out"));
}
}
if let Ok(ref mut stream) = self.stream {
if let Ok(ret) = stream.send_command(command) {
return Ok(ret);
@ -429,6 +448,12 @@ impl ImapConnection {
}
pub fn send_literal(&mut self, data: &[u8]) -> Result<()> {
if let (instant, ref mut status @ Ok(())) = *self.online.lock().unwrap() {
if Instant::now().duration_since(instant) >= std::time::Duration::new(60 * 30, 0) {
*status = Err(MeliError::new("Connection timed out"));
self.stream = Err(MeliError::new("Connection timed out"));
}
}
if let Ok(ref mut stream) = self.stream {
if let Ok(_) = stream.send_literal(data) {
return Ok(());
@ -453,6 +478,12 @@ impl ImapConnection {
}
pub fn send_raw(&mut self, raw: &[u8]) -> Result<()> {
if let (instant, ref mut status @ Ok(())) = *self.online.lock().unwrap() {
if Instant::now().duration_since(instant) >= std::time::Duration::new(60 * 30, 0) {
*status = Err(MeliError::new("Connection timed out"));
self.stream = Err(MeliError::new("Connection timed out"));
}
}
if let Ok(ref mut stream) = self.stream {
if let Ok(_) = stream.send_raw(raw) {
return Ok(());
@ -477,6 +508,12 @@ impl ImapConnection {
}
pub fn set_nonblocking(&mut self, val: bool) -> Result<()> {
if let (instant, ref mut status @ Ok(())) = *self.online.lock().unwrap() {
if Instant::now().duration_since(instant) >= std::time::Duration::new(60 * 30, 0) {
*status = Err(MeliError::new("Connection timed out"));
self.stream = Err(MeliError::new("Connection timed out"));
}
}
if let Ok(ref mut stream) = self.stream {
if let Ok(_) = stream.set_nonblocking(val) {
return Ok(());

38
melib/src/backends/imap/folder.rs

@ -19,7 +19,8 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use crate::backends::{BackendFolder, Folder, FolderHash, FolderPermissions, SpecialUsageMailbox};
use std::sync::{Arc, Mutex};
use crate::error::*;
use std::sync::{Arc, Mutex, RwLock};
#[derive(Debug, Default, Clone)]
pub struct ImapFolder {
@ -28,11 +29,13 @@ pub struct ImapFolder {
pub(super) name: String,
pub(super) parent: Option<FolderHash>,
pub(super) children: Vec<FolderHash>,
pub usage: SpecialUsageMailbox,
pub usage: Arc<RwLock<SpecialUsageMailbox>>,
pub no_select: bool,
pub is_subscribed: bool,
pub permissions: Arc<Mutex<FolderPermissions>>,
pub exists: Arc<Mutex<usize>>,
pub unseen: Arc<Mutex<usize>>,
}
impl BackendFolder for ImapFolder {
@ -57,21 +60,11 @@ impl BackendFolder for ImapFolder {
}
fn clone(&self) -> Folder {
Box::new(ImapFolder {
hash: self.hash,
path: self.path.clone(),
name: self.name.clone(),
parent: self.parent,
children: self.children.clone(),
usage: self.usage,
no_select: self.no_select,
permissions: self.permissions.clone(),
exists: self.exists.clone(),
})
Box::new(std::clone::Clone::clone(self))
}
fn special_usage(&self) -> SpecialUsageMailbox {
self.usage
*self.usage.read().unwrap()
}
fn parent(&self) -> Option<FolderHash> {
@ -81,4 +74,21 @@ impl BackendFolder for ImapFolder {
fn permissions(&self) -> FolderPermissions {
*self.permissions.lock().unwrap()
}
fn is_subscribed(&self) -> bool {
self.is_subscribed
}
fn set_is_subscribed(&mut self, new_val: bool) -> Result<()> {
self.is_subscribed = new_val;
// FIXME: imap subscribe
Ok(())
}
fn set_special_usage(&mut self, new_val: SpecialUsageMailbox) -> Result<()> {
*self.usage.write()? = new_val;
Ok(())
}
fn count(&self) -> Result<(usize, usize)> {
Ok((*self.unseen.lock()?, *self.exists.lock()?))
}
}

6
melib/src/backends/imap/protocol_parser.rs

@ -93,11 +93,11 @@ named!(
if p.eq_ignore_ascii_case(b"\\NoSelect") {
f.no_select = true;
} else if p.eq_ignore_ascii_case(b"\\Sent") {
f.usage = SpecialUsageMailbox::Sent;
let _ = f.set_special_usage(SpecialUsageMailbox::Sent);
} else if p.eq_ignore_ascii_case(b"\\Junk") {
f.usage = SpecialUsageMailbox::Trash;
let _ = f.set_special_usage(SpecialUsageMailbox::Trash);
} else if p.eq_ignore_ascii_case(b"\\Drafts") {
f.usage = SpecialUsageMailbox::Drafts;
let _ = f.set_special_usage(SpecialUsageMailbox::Drafts);
}
}
f.hash = get_path_hash!(path);

17
melib/src/backends/imap/watch.rs

@ -124,7 +124,7 @@ pub fn idle(kit: ImapWatchKit) -> Result<()> {
.read()
.unwrap()
.values()
.find(|f| f.parent.is_none() && (f.usage == SpecialUsageMailbox::Inbox))
.find(|f| f.parent.is_none() && (f.special_usage() == SpecialUsageMailbox::Inbox))
.map(std::clone::Clone::clone)
{
Some(folder) => folder,
@ -166,6 +166,7 @@ pub fn idle(kit: ImapWatchKit) -> Result<()> {
hash: folder_hash,
kind: RefreshEventKind::Rescan,
});
*prev_exists = 0;
uid_store.uid_index.lock().unwrap().clear();
uid_store.hash_index.lock().unwrap().clear();
uid_store.byte_cache.lock().unwrap().clear();
@ -346,6 +347,11 @@ pub fn idle(kit: ImapWatchKit) -> Result<()> {
env.labels_mut().push(hash);
}
}
if !env.is_seen() {
*folder.unseen.lock().unwrap() += 1;
}
*folder.exists.lock().unwrap() += 1;
sender.send(RefreshEvent {
hash: folder_hash,
kind: Create(Box::new(env)),
@ -460,6 +466,9 @@ pub fn idle(kit: ImapWatchKit) -> Result<()> {
env.subject(),
folder.path(),
);
if !env.is_seen() {
*folder.unseen.lock().unwrap() += 1;
}
sender.send(RefreshEvent {
hash: folder_hash,
kind: Create(Box::new(env)),
@ -626,6 +635,9 @@ fn examine_updates(
env.labels_mut().push(hash);
}
}
if !env.is_seen() {
*folder.unseen.lock().unwrap() += 1;
}
sender.send(RefreshEvent {
hash: folder_hash,
kind: Create(Box::new(env)),
@ -697,6 +709,9 @@ fn examine_updates(
env.subject(),
folder.path(),
);
if !env.is_seen() {
*folder.unseen.lock().unwrap() += 1;
}
sender.send(RefreshEvent {
hash: folder_hash,
kind: Create(Box::new(env)),

5
melib/src/backends/jmap.rs

@ -289,7 +289,10 @@ impl JmapType {
s: &AccountSettings,
is_subscribed: Box<dyn Fn(&str) -> bool + Send + Sync>,
) -> Result<Box<dyn MailBackend>> {
let online = Arc::new(Mutex::new(Err(MeliError::new("Account is uninitialised."))));
let online = Arc::new(Mutex::new((
std::time::Instant::now(),
Err(MeliError::new("Account is uninitialised.")),
)));
let server_conf = JmapServerConf::new(s)?;
Ok(Box::new(JmapType {

27
melib/src/backends/jmap/connection.rs

@ -33,10 +33,12 @@ pub struct JmapConnection {
}
impl JmapConnection {
pub fn new(server_conf: &JmapServerConf, online_status: Arc<Mutex<(Instant, Result<()>)>>) -> Result<Self> {
pub fn new(
server_conf: &JmapServerConf,
online_status: Arc<Mutex<(Instant, Result<()>)>>,
) -> Result<Self> {
use reqwest::header;
let mut headers = header::HeaderMap::new();
let connection_start = std::time::Instant::now();
headers.insert(
header::ACCEPT,
header::HeaderValue::from_static("application/json"),
@ -70,28 +72,29 @@ impl JmapConnection {
let res_text = req.text()?;
let session: JmapSession = serde_json::from_str(&res_text).map_err(|_| {
let err = MeliError::new(format!("Could not connect to JMAP server endpoint for {}. Is your server hostname setting correct? (i.e. \"jmap.mailserver.org\") (Note: only session resource discovery via /.well-known/jmap is supported. DNS SRV records are not suppported.)", &server_conf.server_hostname);
*online_status.lock().unwrap() = (Instant::new(), Err(err.clone()));
let err = MeliError::new(format!("Could not connect to JMAP server endpoint for {}. Is your server hostname setting correct? (i.e. \"jmap.mailserver.org\") (Note: only session resource discovery via /.well-known/jmap is supported. DNS SRV records are not suppported.)", &server_conf.server_hostname));
*online_status.lock().unwrap() = (Instant::now(), Err(err.clone()));
err
))?;
}
)?;
if !session
.capabilities
.contains_key("urn:ietf:params:jmap:core")
{
let err = Err(MeliError::new(format!("Server {} did not return JMAP Core capability (urn:ietf:params:jmap:core). Returned capabilities were: {}", &server_conf.server_hostname, session.capabilities.keys().map(String::as_str).collect::<Vec<&str>>().join(", "))));
*online_status.lock().unwrap() = (Instant::new(), Err(err.clone()));
return err;
let err = MeliError::new(format!("Server {} did not return JMAP Core capability (urn:ietf:params:jmap:core). Returned capabilities were: {}", &server_conf.server_hostname, session.capabilities.keys().map(String::as_str).collect::<Vec<&str>>().join(", ")));
*online_status.lock().unwrap() = (Instant::now(), Err(err.clone()));
return Err(err);
}
if !session
.capabilities
.contains_key("urn:ietf:params:jmap:mail")
{
let err = Err(MeliError::new(format!("Server {} does not support JMAP Mail capability (urn:ietf:params:jmap:mail). Returned capabilities were: {}", &server_conf.server_hostname, session.capabilities.keys().map(String::as_str).collect::<Vec<&str>>().join(", "))));
*online_status.lock().unwrap() = (Instant::new(), Err(err.clone()));
return err;
let err = MeliError::new(format!("Server {} does not support JMAP Mail capability (urn:ietf:params:jmap:mail). Returned capabilities were: {}", &server_conf.server_hostname, session.capabilities.keys().map(String::as_str).collect::<Vec<&str>>().join(", ")));
*online_status.lock().unwrap() = (Instant::now(), Err(err.clone()));
return Err(err);
}
*online_status.lock().unwrap() = (Instant::new(), Ok(()));
*online_status.lock().unwrap() = (Instant::now(), Ok(()));
let server_conf = server_conf.clone();
Ok(JmapConnection {
session,

27
melib/src/backends/jmap/folder.rs

@ -21,6 +21,7 @@
use super::*;
use crate::backends::{FolderPermissions, SpecialUsageMailbox};
use std::sync::{Arc, Mutex, RwLock};
#[derive(Debug, Clone)]
pub struct JmapFolder {
@ -34,11 +35,11 @@ pub struct JmapFolder {
pub parent_id: Option<String>,
pub role: Option<String>,
pub sort_order: u64,
pub total_emails: u64,
pub total_emails: Arc<Mutex<u64>>,
pub total_threads: u64,
pub unread_emails: u64,
pub unread_emails: Arc<Mutex<u64>>,
pub unread_threads: u64,
pub usage: SpecialUsageMailbox,
pub usage: Arc<RwLock<SpecialUsageMailbox>>,
}
impl BackendFolder for JmapFolder {
@ -91,4 +92,24 @@ impl BackendFolder for JmapFolder {
None => SpecialUsageMailbox::Normal,
}
}
fn is_subscribed(&self) -> bool {
self.is_subscribed
}
fn set_is_subscribed(&mut self, new_val: bool) -> Result<()> {
self.is_subscribed = new_val;
// FIXME: jmap subscribe
Ok(())
}
fn set_special_usage(&mut self, new_val: SpecialUsageMailbox) -> Result<()> {
*self.usage.write()? = new_val;
Ok(())
}
fn count(&self) -> Result<(usize, usize)> {
Ok((
*self.unread_emails.lock()? as usize,
*self.total_emails.lock()? as usize,
))
}
}

2
melib/src/backends/jmap/operations.rs

@ -105,7 +105,7 @@ impl BackendOp for JmapOp {
Ok(())
}
fn set_tag(&mut self, _envelope: &mut Envelope, _tag: String, value: bool) -> Result<()> {
fn set_tag(&mut self, _envelope: &mut Envelope, _tag: String, _value: bool) -> Result<()> {
Ok(())
}
}

8
melib/src/backends/jmap/protocol.rs

@ -126,7 +126,7 @@ pub fn get_mailboxes(conn: &JmapConnection) -> Result<FnvHashMap<FolderHash, Jma
let res_text = res?.text()?;
let mut v: MethodResponse = serde_json::from_str(&res_text).unwrap();
*conn.online_status.lock().unwrap() = true;
*conn.online_status.lock().unwrap() = (std::time::Instant::now(), Ok(()));
let m = GetResponse::<MailboxObject>::try_from(v.method_responses.remove(0))?;
let GetResponse::<MailboxObject> {
list, account_id, ..
@ -163,9 +163,9 @@ pub fn get_mailboxes(conn: &JmapConnection) -> Result<FnvHashMap<FolderHash, Jma
role,
usage: Default::default(),
sort_order,
total_emails,
total_emails: Arc::new(Mutex::new(total_emails)),
total_threads,
unread_emails,
unread_emails: Arc::new(Mutex::new(unread_emails)),
unread_threads,
},
)
@ -208,7 +208,7 @@ pub fn get_message_list(conn: &JmapConnection, folder: &JmapFolder) -> Result<Ve
let res_text = res?.text()?;
let mut v: MethodResponse = serde_json::from_str(&res_text).unwrap();
*conn.online_status.lock().unwrap() = true;
*conn.online_status.lock().unwrap() = (std::time::Instant::now(), Ok(()));
let m = QueryResponse::<EmailObject>::try_from(v.method_responses.remove(0))?;
let QueryResponse::<EmailObject> { ids, .. } = m;
Ok(ids)

43
melib/src/backends/maildir.rs

@ -33,6 +33,7 @@ use std::collections::hash_map::DefaultHasher;
use std::fs;
use std::hash::{Hash, Hasher};
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
/// `BackendOp` implementor for Maildir
#[derive(Debug)]
@ -168,7 +169,7 @@ impl<'a> BackendOp for MaildirOp {
}
}
#[derive(Debug, Default)]
#[derive(Debug, Default, Clone)]
pub struct MaildirFolder {
hash: FolderHash,
name: String,
@ -176,8 +177,11 @@ pub struct MaildirFolder {
path: PathBuf,
parent: Option<FolderHash>,
children: Vec<FolderHash>,
usage: SpecialUsageMailbox,
pub usage: Arc<RwLock<SpecialUsageMailbox>>,
pub is_subscribed: bool,
permissions: FolderPermissions,
pub total: Arc<Mutex<usize>>,
pub unseen: Arc<Mutex<usize>>,
}
impl MaildirFolder {
@ -238,7 +242,8 @@ impl MaildirFolder {
fs_path: pathbuf,
parent,
children,
usage: SpecialUsageMailbox::Normal,
usage: Arc::new(RwLock::new(SpecialUsageMailbox::Normal)),
is_subscribed: true,
permissions: FolderPermissions {
create_messages: !read_only,
remove_messages: !read_only,
@ -249,6 +254,8 @@ impl MaildirFolder {
delete_mailbox: !read_only,
change_permissions: false,
},
unseen: Arc::new(Mutex::new(0)),
total: Arc::new(Mutex::new(0)),
};
ret.is_valid()?;
Ok(ret)
@ -274,6 +281,7 @@ impl MaildirFolder {
Ok(())
}
}
impl BackendFolder for MaildirFolder {
fn hash(&self) -> FolderHash {
self.hash
@ -296,20 +304,11 @@ impl BackendFolder for MaildirFolder {
}
fn clone(&self) -> Folder {
Box::new(MaildirFolder {
hash: self.hash,
name: self.name.clone(),
fs_path: self.fs_path.clone(),
path: self.path.clone(),
children: self.children.clone(),
usage: self.usage,
parent: self.parent,
permissions: self.permissions,
})
Box::new(std::clone::Clone::clone(self))
}
fn special_usage(&self) -> SpecialUsageMailbox {
self.usage
*self.usage.read().unwrap()
}
fn parent(&self) -> Option<FolderHash> {
@ -319,4 +318,20 @@ impl BackendFolder for MaildirFolder {
fn permissions(&self) -> FolderPermissions {
self.permissions
}
fn is_subscribed(&self) -> bool {
self.is_subscribed
}
fn set_is_subscribed(&mut self, new_val: bool) -> Result<()> {
self.is_subscribed = new_val;
Ok(())
}
fn set_special_usage(&mut self, new_val: SpecialUsageMailbox) -> Result<()> {
*self.usage.write()? = new_val;
Ok(())
}
fn count(&self) -> Result<(usize, usize)> {
Ok((*self.unseen.lock()?, *self.total.lock()?))
}
}

31
melib/src/backends/maildir/backend.rs

@ -192,7 +192,7 @@ impl MailBackend for MaildirType {
Ok(self
.folders
.iter()
.map(|(h, f)| (*h, f.clone() as Folder))
.map(|(h, f)| (*h, BackendFolder::clone(f)))
.collect())
}
@ -342,6 +342,11 @@ impl MailBackend for MaildirType {
debug!("watching {:?}", root_path);
let hash_indexes = self.hash_indexes.clone();
let folder_index = self.folder_index.clone();
let folder_counts = self
.folders
.iter()
.map(|(&k, v)| (k, (v.unseen.clone(), v.total.clone())))
.collect::<FnvHashMap<FolderHash, (Arc<Mutex<usize>>, Arc<Mutex<usize>>)>>();
let handle = thread::Builder::new()
.name("folder watch".to_string())
.spawn(move || {
@ -397,6 +402,10 @@ impl MailBackend for MaildirType {
env.subject(),
pathbuf.display()
);
if !env.is_seen() {
*folder_counts[&folder_hash].0.lock().unwrap() += 1;
}
*folder_counts[&folder_hash].1.lock().unwrap() += 1;
sender.send(RefreshEvent {
hash: folder_hash,
kind: Create(Box::new(env)),
@ -505,6 +514,8 @@ impl MailBackend for MaildirType {
e.removed = true;
});
//FIXME: check if envelope was unseen to update unseen count
*folder_counts[&folder_hash].1.lock().unwrap() += 1;
sender.send(RefreshEvent {
hash: folder_hash,
kind: Remove(hash),
@ -579,6 +590,10 @@ impl MailBackend for MaildirType {
env.subject(),
dest.display()
);
if !env.is_seen() {
*folder_counts[&folder_hash].0.lock().unwrap() += 1;
}
*folder_counts[&folder_hash].1.lock().unwrap() += 1;
sender.send(RefreshEvent {
hash: folder_hash,
kind: Create(Box::new(env)),
@ -777,6 +792,8 @@ impl MaildirType {
// TODO: Avoid clone
let folder: &MaildirFolder = &self.folders[&self.owned_folder_idx(folder)];
let folder_hash = folder.hash();
let unseen = folder.unseen.clone();
let total = folder.total.clone();
let tx_final = w.tx();
let path: PathBuf = folder.fs_path().into();
let name = format!("parsing {:?}", folder.name());
@ -785,6 +802,8 @@ impl MaildirType {
let folder_index = self.folder_index.clone();
let closure = move |work_context: crate::async_workers::WorkContext| {
let unseen = unseen.clone();
let total = total.clone();
let name = name.clone();
work_context
.set_name
@ -829,6 +848,8 @@ impl MaildirType {
count
};
for chunk in files.chunks(chunk_size) {
let unseen = unseen.clone();
let total = total.clone();
let cache_dir = cache_dir.clone();
let tx = tx.clone();
let map = map.clone();
@ -869,6 +890,10 @@ impl MaildirType {
.lock()
.unwrap()
.insert(hash, folder_hash);
if !env.is_seen() {
*unseen.lock().unwrap() += 1;
}
*total.lock().unwrap() += 1;
local_r.push(env);
continue;
}
@ -908,6 +933,10 @@ impl MaildirType {
let writer = io::BufWriter::new(f);
bincode::serialize_into(writer, &e).unwrap();
}
if !e.is_seen() {
*unseen.lock().unwrap() += 1;
}
*total.lock().unwrap() += 1;
local_r.push(e);
} else {
debug!(

31
melib/src/backends/mbox.rs

@ -49,7 +49,7 @@ use std::io::Read;
use std::os::unix::io::AsRawFd;
use std::path::{Path, PathBuf};
use std::sync::mpsc::channel;
use std::sync::{Arc, Mutex};
use std::sync::{Arc, Mutex, RwLock};
const F_OFD_SETLKW: libc::c_int = 38;
@ -87,7 +87,11 @@ struct MboxFolder {
content: Vec<u8>,
children: Vec<FolderHash>,
parent: Option<FolderHash>,
usage: Arc<RwLock<SpecialUsageMailbox>>,
is_subscribed: bool,
permissions: FolderPermissions,
pub total: Arc<Mutex<usize>>,
pub unseen: Arc<Mutex<usize>>,
}
impl BackendFolder for MboxFolder {
@ -115,8 +119,12 @@ impl BackendFolder for MboxFolder {
path: self.path.clone(),
content: self.content.clone(),
children: self.children.clone(),
usage: self.usage.clone(),
is_subscribed: self.is_subscribed,
parent: self.parent,
permissions: self.permissions,
unseen: self.unseen.clone(),
total: self.total.clone(),
})
}
@ -129,12 +137,27 @@ impl BackendFolder for MboxFolder {
}
fn special_usage(&self) -> SpecialUsageMailbox {
SpecialUsageMailbox::Normal
*self.usage.read().unwrap()
}
fn permissions(&self) -> FolderPermissions {
self.permissions
}
fn is_subscribed(&self) -> bool {
self.is_subscribed
}
fn set_is_subscribed(&mut self, new_val: bool) -> Result<()> {
self.is_subscribed = new_val;
Ok(())
}
fn set_special_usage(&mut self, new_val: SpecialUsageMailbox) -> Result<()> {
*self.usage.write()? = new_val;
Ok(())
}
fn count(&self) -> Result<(usize, usize)> {
Ok((*self.unseen.lock()?, *self.total.lock()?))
}
}
/// `BackendOp` implementor for Mbox
@ -631,6 +654,8 @@ impl MboxType {
content: Vec::new(),
children: Vec::new(),
parent: None,
usage: Arc::new(RwLock::new(SpecialUsageMailbox::Normal)),
is_subscribed: true,
permissions: FolderPermissions {
create_messages: !read_only,
remove_messages: !read_only,
@ -641,6 +666,8 @@ impl MboxType {
delete_mailbox: !read_only,
change_permissions: false,
},
unseen: Arc::new(Mutex::new(0)),
total: Arc::new(Mutex::new(0)),
},
);
/*

30
melib/src/backends/notmuch.rs

@ -16,7 +16,7 @@ use std::ffi::{CStr, CString};
use std::hash::{Hash, Hasher};
use std::io::Read;
use std::path::{Path, PathBuf};
use std::sync::{Arc, RwLock};
use std::sync::{Arc, Mutex, RwLock};
pub mod bindings;
use bindings::*;
@ -67,10 +67,13 @@ struct NotmuchFolder {
parent: Option<FolderHash>,
name: String,
path: String,
usage: SpecialUsageMailbox,
query_str: String,
query: Option<*mut notmuch_query_t>,
phantom: std::marker::PhantomData<&'static mut notmuch_query_t>,
usage: Arc<RwLock<SpecialUsageMailbox>>,
total: Arc<Mutex<usize>>,
unseen: Arc<Mutex<usize>>,
}
impl BackendFolder for NotmuchFolder {
@ -101,12 +104,29 @@ impl BackendFolder for NotmuchFolder {
}
fn special_usage(&self) -> SpecialUsageMailbox {
self.usage
*self.usage.read().unwrap()
}
fn permissions(&self) -> FolderPermissions {
FolderPermissions::default()
}
fn is_subscribed(&self) -> bool {
true
}
fn set_is_subscribed(&mut self, _new_val: bool) -> Result<()> {
Ok(())
}
fn set_special_usage(&mut self, new_val: SpecialUsageMailbox) -> Result<()> {
*self.usage.write()? = new_val;
Ok(())
}
fn count(&self) -> Result<(usize, usize)> {
Ok((*self.unseen.lock()?, *self.total.lock()?))
}
}
unsafe impl Send for NotmuchFolder {}
@ -162,8 +182,10 @@ impl NotmuchDb {
parent: None,
query: None,
query_str: query_str.to_string(),
usage: SpecialUsageMailbox::Normal,
usage: Arc::new(RwLock::new(SpecialUsageMailbox::Normal)),
phantom: std::marker::PhantomData,
total: Arc::new(Mutex::new(0)),
unseen: Arc::new(Mutex::new(0)),
},
);
} else {

28
ui/src/components/mail/listing.rs

@ -770,17 +770,11 @@ impl Component for Listing {
} else {
return Some(String::new());
};
let envelopes = account.collection.envelopes.clone();
let envelopes = envelopes.read().unwrap();
format!(
"Mailbox: {}, Messages: {}, New: {}",
m.folder.name(),
m.envelopes.len(),
m.envelopes
.iter()
.map(|h| &envelopes[&h])
.filter(|e| !e.is_seen())
.count()
m.folder.count().ok().map(|(v, _)| v).unwrap_or(0),
)
})
}
@ -903,20 +897,12 @@ impl Listing {
) {
match context.accounts[index].status(entries[&folder_idx].hash()) {
Ok(_) => {
let account = &context.accounts[index];
let count = account[entries[&folder_idx].hash()]
.unwrap()
.envelopes
.iter()
.filter_map(|&h| {
if account.collection.get_env(h).is_seen() {
None
} else {
Some(())
}
})
.count();
lines.push((*depth, *inc, folder_idx, Some(count)));
lines.push((
*depth,
*inc,
folder_idx,
entries[&folder_idx].count().ok().map(|(v, _)| v),
));
}
Err(_) => {
lines.push((*depth, *inc, folder_idx, None));

44
ui/src/components/mail/status.rs

@ -291,18 +291,12 @@ impl StatusPanel {
self.content[(2, h + y + 1)].set_ch(' ');
}
}
let count = a.ref_folders.values().fold((0, 0), |acc, f| {
let count = f.count().unwrap_or((0, 0));
(acc.0 + count.0, acc.1 + count.1)
});
let (mut column_width, _) = write_string_to_grid(
&format!(
"Messages total {}, unseen {}",
a.collection.len(),
a.collection
.envelopes
.read()
.unwrap()
.values()
.filter(|e| !e.is_seen())
.count()
),
&format!("Messages total {}, unseen {}", count.1, count.0),
&mut self.content,
Color::Default,
Color::Default,
@ -338,17 +332,7 @@ impl StatusPanel {
);
/* next column */
write_string_to_grid(
&format!(
"Messages total {}, unseen {}",
a.collection.len(),
a.collection
.envelopes
.read()
.unwrap()
.values()
.filter(|e| !e.is_seen())
.count()
),
"Special Mailboxes:",
&mut self.content,
Color::Default,
Color::Default,
@ -356,6 +340,22 @@ impl StatusPanel {
((5 + column_width, y + 2), (120 - 2, y + 2)),
None,
);
for (i, f) in a
.ref_folders
.values()
.filter(|f| f.special_usage() != SpecialUsageMailbox::Normal)
.enumerate()
{
write_string_to_grid(
&format!("{}: {}", f.path(), f.special_usage()),
&mut self.content,
Color::Default,
Color::Default,
Attr::Default,
((5 + column_width, y + 3 + i), (120 - 2, y + 2)),
None,
);
}
}
}
}

51
ui/src/conf/accounts.rs

@ -146,6 +146,7 @@ pub struct Account {
name: String,
pub is_online: bool,
pub(crate) folders: FnvHashMap<FolderHash, MailboxEntry>,
pub(crate) ref_folders: FnvHashMap<FolderHash, Folder>,
pub(crate) folder_confs: FnvHashMap<FolderHash, FileFolderConf>,
pub(crate) folders_order: Vec<FolderHash>,
pub(crate) folder_names: FnvHashMap<FolderHash, String>,
@ -289,6 +290,7 @@ impl Account {
name,
is_online: false,
folders: Default::default(),
ref_folders: Default::default(),
folder_confs: Default::default(),
folders_order: Default::default(),
folder_names: Default::default(),
@ -309,7 +311,7 @@ impl Account {
}
fn init(&mut self) {
let ref_folders: FnvHashMap<FolderHash, Folder> =
let mut ref_folders: FnvHashMap<FolderHash, Folder> =
match self.backend.read().unwrap().folders() {
Ok(f) => f,
Err(err) => {
@ -325,7 +327,7 @@ impl Account {
let mut folder_confs = FnvHashMap::default();
let mut sent_folder = None;
for f in ref_folders.values() {
for f in ref_folders.values_mut() {
if !((self.settings.folder_confs.contains_key(f.path())
&& self.settings.folder_confs[f.path()]
.folder_conf()
@ -342,18 +344,41 @@ impl Account {
continue;
}
if self.settings.folder_confs.contains_key(f.path()) {
match self.settings.folder_confs[f.path()].folder_conf().usage {
if let Some(conf) = self.settings.folder_confs.get_mut(f.path()) {
conf.folder_conf.usage = if f.special_usage() != SpecialUsageMailbox::Normal {
Some(f.special_usage())
} else {
let tmp = SpecialUsageMailbox::detect_usage(f.name());
if tmp != Some(SpecialUsageMailbox::Normal) && tmp != None {
let _ = f.set_special_usage(tmp.unwrap());
}
tmp
};
match conf.folder_conf.usage {
Some(SpecialUsageMailbox::Sent) => {
sent_folder = Some(f.hash());
}
None => {
if f.special_usage() == SpecialUsageMailbox::Sent {
sent_folder = Some(f.hash());
}
}
_ => {}
}
folder_confs.insert(f.hash(), self.settings.folder_confs[f.path()].clone());
folder_confs.insert(f.hash(), conf.clone());
} else {
let mut new = FileFolderConf::default();
new.folder_conf.subscribe = super::ToggleFlag::InternalVal(true);
new.folder_conf.usage = SpecialUsageMailbox::detect_usage(f.name());
new.folder_conf.usage = if f.special_usage() != SpecialUsageMailbox::Normal {
Some(f.special_usage())
} else {
let tmp = SpecialUsageMailbox::detect_usage(f.name());
if tmp != Some(SpecialUsageMailbox::Normal) && tmp != None {
let _ = f.set_special_usage(tmp.unwrap());
}
tmp
};
folder_confs.insert(f.hash(), new);
}
folder_names.insert(f.hash(), f.path().to_string());
@ -396,7 +421,6 @@ impl Account {
workers.insert(
*h,
Account::new_worker(
&folder_confs,
f.clone(),
&mut self.backend,
&self.work_context,
@ -440,6 +464,7 @@ impl Account {
}
self.folders = folders;
self.ref_folders = ref_folders;
self.folder_confs = folder_confs;
self.folders_order = folders_order;
self.folder_names = folder_names;
@ -450,7 +475,6 @@ impl Account {
}
fn new_worker(
folder_confs: &FnvHashMap<FolderHash, FileFolderConf>,
folder: Folder,
backend: &Arc<RwLock<Box<dyn MailBackend>>>,
work_context: &WorkContext,
@ -460,11 +484,11 @@ impl Account {
let mut builder = AsyncBuilder::new();
let our_tx = builder.tx();
let folder_hash = folder.hash();
let priority = match folder_confs[&folder.hash()].folder_conf().usage {
Some(SpecialUsageMailbox::Inbox) => 0,
Some(SpecialUsageMailbox::Sent) => 1,
Some(SpecialUsageMailbox::Drafts) | Some(SpecialUsageMailbox::Trash) => 2,
Some(_) | None => {
let priority = match folder.special_usage() {
SpecialUsageMailbox::Inbox => 0,
SpecialUsageMailbox::Sent => 1,
SpecialUsageMailbox::Drafts | SpecialUsageMailbox::Trash => 2,
_ => {
3 * folder
.path()
.split(if folder.path().contains('/') {
@ -673,7 +697,6 @@ impl Account {
let ref_folders: FnvHashMap<FolderHash, Folder> =
self.backend.read().unwrap().folders().unwrap();
let handle = Account::new_worker(
&self.folder_confs,
ref_folders[&folder_hash].clone(),
&mut self.backend,
&self.work_context,

Loading…
Cancel
Save