Add account online status

Add a boolean field to accounts that states if the account can be
accessed. Local backends (Maildir/mbox) return true every time, but
remote backends (IMAP) may not. Accounts start as offline and then get
initialised when their status goes to online. Right now if an IMAP
account startup but later get offline, there are crashes. With this
change the account can be switched back to offline when that happens.
embed
Manos Pitsidianakis 2019-10-24 20:30:17 +03:00
parent 9ef9293a45
commit e5b6faf6bd
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
9 changed files with 249 additions and 150 deletions

View File

@ -39,7 +39,7 @@ use crossbeam::{
use std::fmt; use std::fmt;
use std::sync::Arc; use std::sync::Arc;
#[derive(Clone)] #[derive(Clone, Debug)]
pub struct WorkContext { pub struct WorkContext {
pub new_work: Sender<Work>, pub new_work: Sender<Work>,
pub set_name: Sender<(std::thread::ThreadId, String)>, pub set_name: Sender<(std::thread::ThreadId, String)>,

View File

@ -44,7 +44,7 @@ use fnv::FnvHashMap;
use std; use std;
pub type BackendCreator = pub type BackendCreator =
Box<dyn Fn(&AccountSettings, Box<dyn Fn(&str) -> bool>) -> Box<dyn MailBackend>>; Box<dyn Fn(&AccountSettings, Box<dyn Fn(&str) -> bool + Send + Sync>) -> Box<dyn MailBackend>>;
/// A hashmap containing all available mail backends. /// A hashmap containing all available mail backends.
/// An abstraction over any available backends. /// An abstraction over any available backends.
@ -108,7 +108,7 @@ pub enum RefreshEventKind {
/// Rename(old_hash, new_hash) /// Rename(old_hash, new_hash)
Rename(EnvelopeHash, EnvelopeHash), Rename(EnvelopeHash, EnvelopeHash),
Create(Box<Envelope>), Create(Box<Envelope>),
Remove(FolderHash), Remove(EnvelopeHash),
Rescan, Rescan,
Failure(MeliError), Failure(MeliError),
} }
@ -177,6 +177,7 @@ pub enum FolderOperation {
type NewFolderName = String; type NewFolderName = String;
pub trait MailBackend: ::std::fmt::Debug { pub trait MailBackend: ::std::fmt::Debug {
fn is_online(&self) -> bool;
fn get(&mut self, folder: &Folder) -> Async<Result<Vec<Envelope>>>; fn get(&mut self, folder: &Folder) -> Async<Result<Vec<Envelope>>>;
fn watch( fn watch(
&self, &self,

View File

@ -65,14 +65,30 @@ pub struct ImapServerConf {
pub danger_accept_invalid_certs: bool, pub danger_accept_invalid_certs: bool,
} }
struct IsSubscribedFn(Box<dyn Fn(&str) -> bool + Send + Sync>);
impl std::fmt::Debug for IsSubscribedFn {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "IsSubscribedFn Box")
}
}
impl std::ops::Deref for IsSubscribedFn {
type Target = Box<dyn Fn(&str) -> bool + Send + Sync>;
fn deref(&self) -> &Box<dyn Fn(&str) -> bool + Send + Sync> {
&self.0
}
}
type Capabilities = FnvHashSet<Vec<u8>>; type Capabilities = FnvHashSet<Vec<u8>>;
#[derive(Debug)] #[derive(Debug)]
pub struct ImapType { pub struct ImapType {
account_name: String, account_name: String,
online: Arc<Mutex<bool>>,
is_subscribed: Arc<IsSubscribedFn>,
connection: Arc<Mutex<ImapConnection>>, connection: Arc<Mutex<ImapConnection>>,
server_conf: ImapServerConf, server_conf: ImapServerConf,
folders: FnvHashMap<FolderHash, ImapFolder>, folders: Arc<Mutex<FnvHashMap<FolderHash, ImapFolder>>>,
hash_index: Arc<Mutex<FnvHashMap<EnvelopeHash, (UID, FolderHash)>>>, hash_index: Arc<Mutex<FnvHashMap<EnvelopeHash, (UID, FolderHash)>>>,
uid_index: Arc<Mutex<FnvHashMap<usize, EnvelopeHash>>>, uid_index: Arc<Mutex<FnvHashMap<usize, EnvelopeHash>>>,
@ -80,6 +96,9 @@ pub struct ImapType {
} }
impl MailBackend for ImapType { impl MailBackend for ImapType {
fn is_online(&self) -> bool {
*self.online.lock().unwrap()
}
fn get(&mut self, folder: &Folder) -> Async<Result<Vec<Envelope>>> { fn get(&mut self, folder: &Folder) -> Async<Result<Vec<Envelope>>> {
macro_rules! exit_on_error { macro_rules! exit_on_error {
($tx:expr,$($result:expr)+) => { ($tx:expr,$($result:expr)+) => {
@ -97,7 +116,7 @@ impl MailBackend for ImapType {
let uid_index = self.uid_index.clone(); let uid_index = self.uid_index.clone();
let folder_path = folder.path().to_string(); let folder_path = folder.path().to_string();
let folder_hash = folder.hash(); let folder_hash = folder.hash();
let folder_exists = self.folders[&folder_hash].exists.clone(); let folder_exists = self.folders.lock().unwrap()[&folder_hash].exists.clone();
let connection = self.connection.clone(); let connection = self.connection.clone();
let closure = move |_work_context| { let closure = move |_work_context| {
let connection = connection.clone(); let connection = connection.clone();
@ -189,6 +208,7 @@ impl MailBackend for ImapType {
let folders = self.folders.clone(); let folders = self.folders.clone();
let conn = ImapConnection::new_connection(&self.server_conf); let conn = ImapConnection::new_connection(&self.server_conf);
let main_conn = self.connection.clone(); let main_conn = self.connection.clone();
let is_online = self.online.clone();
let hash_index = self.hash_index.clone(); let hash_index = self.hash_index.clone();
let uid_index = self.uid_index.clone(); let uid_index = self.uid_index.clone();
let handle = std::thread::Builder::new() let handle = std::thread::Builder::new()
@ -201,6 +221,7 @@ impl MailBackend for ImapType {
.unwrap(); .unwrap();
let kit = ImapWatchKit { let kit = ImapWatchKit {
conn, conn,
is_online,
main_conn, main_conn,
hash_index, hash_index,
uid_index, uid_index,
@ -218,55 +239,20 @@ impl MailBackend for ImapType {
} }
fn folders(&self) -> FnvHashMap<FolderHash, Folder> { fn folders(&self) -> FnvHashMap<FolderHash, Folder> {
if !self.folders.is_empty() { let mut folders = self.folders.lock().unwrap();
return self if !folders.is_empty() {
.folders return folders
.iter() .iter()
.map(|(h, f)| (*h, Box::new(Clone::clone(f)) as Folder)) .map(|(h, f)| (*h, Box::new(Clone::clone(f)) as Folder))
.collect(); .collect();
} }
*folders = ImapType::imap_folders(&self.connection);
let mut folders: FnvHashMap<FolderHash, ImapFolder> = Default::default(); folders.retain(|_, f| (self.is_subscribed)(f.path()));
let mut res = String::with_capacity(8 * 1024); let keys = folders.keys().cloned().collect::<FnvHashSet<FolderHash>>();
let mut conn = self.connection.lock().unwrap(); for f in folders.values_mut() {
conn.send_command(b"LIST \"\" \"*\"").unwrap(); f.children.retain(|c| keys.contains(c));
conn.read_response(&mut res).unwrap();
debug!("out: {}", &res);
for l in res.lines().map(|l| l.trim()) {
if let Ok(mut folder) =
protocol_parser::list_folder_result(l.as_bytes()).to_full_result()
{
if let Some(parent) = folder.parent {
if folders.contains_key(&parent) {
folders
.entry(parent)
.and_modify(|e| e.children.push(folder.hash));
} else {
/* Insert dummy parent entry, populating only the children field. Later
* when we encounter the parent entry we will swap its children with
* dummy's */
folders.insert(
parent,
ImapFolder {
children: vec![folder.hash],
..ImapFolder::default()
},
);
}
}
if folders.contains_key(&folder.hash) {
let entry = folders.entry(folder.hash).or_default();
std::mem::swap(&mut entry.children, &mut folder.children);
std::mem::swap(entry, &mut folder);
} else {
folders.insert(folder.hash, folder);
}
} else {
debug!("parse error for {:?}", l);
}
} }
debug!(&folders); *self.online.lock().unwrap() = true;
folders folders
.iter() .iter()
.map(|(h, f)| (*h, Box::new(Clone::clone(f)) as Folder)) .map(|(h, f)| (*h, Box::new(Clone::clone(f)) as Folder))
@ -277,25 +263,31 @@ impl MailBackend for ImapType {
let (uid, folder_hash) = self.hash_index.lock().unwrap()[&hash]; let (uid, folder_hash) = self.hash_index.lock().unwrap()[&hash];
Box::new(ImapOp::new( Box::new(ImapOp::new(
uid, uid,
self.folders[&folder_hash].path().to_string(), self.folders.lock().unwrap()[&folder_hash]
.path()
.to_string(),
self.connection.clone(), self.connection.clone(),
self.byte_cache.clone(), self.byte_cache.clone(),
)) ))
} }
fn save(&self, bytes: &[u8], folder: &str, flags: Option<Flag>) -> Result<()> { fn save(&self, bytes: &[u8], folder: &str, flags: Option<Flag>) -> Result<()> {
let path = self let path = {
.folders let folders = self.folders.lock().unwrap();
.values()
.find(|v| v.name == folder) folders
.ok_or(MeliError::new(""))?; .values()
.find(|v| v.name == folder)
.map(|v| v.path().to_string())
.ok_or(MeliError::new(""))?
};
let mut response = String::with_capacity(8 * 1024); let mut response = String::with_capacity(8 * 1024);
let mut conn = self.connection.lock().unwrap(); let mut conn = self.connection.lock().unwrap();
let flags = flags.unwrap_or(Flag::empty()); let flags = flags.unwrap_or(Flag::empty());
conn.send_command( conn.send_command(
format!( format!(
"APPEND \"{}\" ({}) {{{}}}", "APPEND \"{}\" ({}) {{{}}}",
path.path(), &path,
flags_to_imap_list!(flags), flags_to_imap_list!(flags),
bytes.len() bytes.len()
) )
@ -311,7 +303,14 @@ impl MailBackend for ImapType {
fn folder_operation(&mut self, path: &str, op: FolderOperation) -> Result<()> { fn folder_operation(&mut self, path: &str, op: FolderOperation) -> Result<()> {
use FolderOperation::*; use FolderOperation::*;
match (&op, self.folders.values().any(|f| f.path == path)) { match (
&op,
self.folders
.lock()
.unwrap()
.values()
.any(|f| f.path == path),
) {
(Create, true) => { (Create, true) => {
return Err(MeliError::new(format!( return Err(MeliError::new(format!(
"Folder named `{}` in account `{}` already exists.", "Folder named `{}` in account `{}` already exists.",
@ -391,7 +390,10 @@ macro_rules! get_conf_val {
} }
impl ImapType { impl ImapType {
pub fn new(s: &AccountSettings, is_subscribed: Box<dyn Fn(&str) -> bool>) -> Self { pub fn new(
s: &AccountSettings,
is_subscribed: Box<dyn Fn(&str) -> bool + Send + Sync>,
) -> Self {
debug!(s); debug!(s);
let server_hostname = get_conf_val!(s["server_hostname"]); let server_hostname = get_conf_val!(s["server_hostname"]);
let server_username = get_conf_val!(s["server_username"]); let server_username = get_conf_val!(s["server_username"]);
@ -416,28 +418,18 @@ impl ImapType {
}; };
let connection = ImapConnection::new_connection(&server_conf); let connection = ImapConnection::new_connection(&server_conf);
let mut m = ImapType { ImapType {
account_name: s.name().to_string(), account_name: s.name().to_string(),
online: Arc::new(Mutex::new(false)),
server_conf, server_conf,
is_subscribed: Arc::new(IsSubscribedFn(is_subscribed)),
folders: Default::default(), folders: Arc::new(Mutex::new(Default::default())),
connection: Arc::new(Mutex::new(connection)), connection: Arc::new(Mutex::new(connection)),
hash_index: Default::default(), hash_index: Default::default(),
uid_index: Default::default(), uid_index: Default::default(),
byte_cache: Default::default(), byte_cache: Default::default(),
};
m.folders = m.imap_folders();
m.folders.retain(|_, f| is_subscribed(f.path()));
let keys = m
.folders
.keys()
.cloned()
.collect::<FnvHashSet<FolderHash>>();
for f in m.folders.values_mut() {
f.children.retain(|c| keys.contains(c));
} }
m
} }
pub fn shell(&mut self) { pub fn shell(&mut self) {
@ -472,10 +464,12 @@ impl ImapType {
} }
} }
pub fn imap_folders(&self) -> FnvHashMap<FolderHash, ImapFolder> { pub fn imap_folders(
connection: &Arc<Mutex<ImapConnection>>,
) -> FnvHashMap<FolderHash, ImapFolder> {
let mut folders: FnvHashMap<FolderHash, ImapFolder> = Default::default(); let mut folders: FnvHashMap<FolderHash, ImapFolder> = Default::default();
let mut res = String::with_capacity(8 * 1024); let mut res = String::with_capacity(8 * 1024);
let mut conn = self.connection.lock().unwrap(); let mut conn = connection.lock().unwrap();
conn.send_command(b"LIST \"\" \"*\"").unwrap(); conn.send_command(b"LIST \"\" \"*\"").unwrap();
conn.read_response(&mut res).unwrap(); conn.read_response(&mut res).unwrap();
debug!("out: {}", &res); debug!("out: {}", &res);

View File

@ -24,10 +24,11 @@ use std::sync::{Arc, Mutex};
/// Arguments for IMAP watching functions /// Arguments for IMAP watching functions
pub struct ImapWatchKit { pub struct ImapWatchKit {
pub conn: ImapConnection, pub conn: ImapConnection,
pub is_online: Arc<Mutex<bool>>,
pub main_conn: Arc<Mutex<ImapConnection>>, pub main_conn: Arc<Mutex<ImapConnection>>,
pub hash_index: Arc<Mutex<FnvHashMap<EnvelopeHash, (UID, FolderHash)>>>, pub hash_index: Arc<Mutex<FnvHashMap<EnvelopeHash, (UID, FolderHash)>>>,
pub uid_index: Arc<Mutex<FnvHashMap<usize, EnvelopeHash>>>, pub uid_index: Arc<Mutex<FnvHashMap<usize, EnvelopeHash>>>,
pub folders: FnvHashMap<FolderHash, ImapFolder>, pub folders: Arc<Mutex<FnvHashMap<FolderHash, ImapFolder>>>,
pub sender: RefreshEventConsumer, pub sender: RefreshEventConsumer,
pub work_context: WorkContext, pub work_context: WorkContext,
} }
@ -49,6 +50,7 @@ macro_rules! exit_on_error {
pub fn poll_with_examine(kit: ImapWatchKit) { pub fn poll_with_examine(kit: ImapWatchKit) {
debug!("poll with examine"); debug!("poll with examine");
let ImapWatchKit { let ImapWatchKit {
is_online,
mut conn, mut conn,
main_conn, main_conn,
hash_index, hash_index,
@ -57,6 +59,12 @@ pub fn poll_with_examine(kit: ImapWatchKit) {
sender, sender,
work_context, work_context,
} = kit; } = kit;
loop {
if *is_online.lock().unwrap() {
break;
}
std::thread::sleep(std::time::Duration::from_millis(100));
}
let mut response = String::with_capacity(8 * 1024); let mut response = String::with_capacity(8 * 1024);
let thread_id: std::thread::ThreadId = std::thread::current().id(); let thread_id: std::thread::ThreadId = std::thread::current().id();
loop { loop {
@ -65,6 +73,7 @@ pub fn poll_with_examine(kit: ImapWatchKit) {
.send((thread_id, "sleeping...".to_string())) .send((thread_id, "sleeping...".to_string()))
.unwrap(); .unwrap();
std::thread::sleep(std::time::Duration::from_millis(5 * 60 * 1000)); std::thread::sleep(std::time::Duration::from_millis(5 * 60 * 1000));
let folders = folders.lock().unwrap();
for folder in folders.values() { for folder in folders.values() {
work_context work_context
.set_status .set_status
@ -94,6 +103,7 @@ pub fn idle(kit: ImapWatchKit) {
* minutes wake up and poll the others */ * minutes wake up and poll the others */
let ImapWatchKit { let ImapWatchKit {
mut conn, mut conn,
is_online,
main_conn, main_conn,
hash_index, hash_index,
uid_index, uid_index,
@ -101,10 +111,19 @@ pub fn idle(kit: ImapWatchKit) {
sender, sender,
work_context, work_context,
} = kit; } = kit;
loop {
if *is_online.lock().unwrap() {
break;
}
std::thread::sleep(std::time::Duration::from_millis(100));
}
let thread_id: std::thread::ThreadId = std::thread::current().id(); let thread_id: std::thread::ThreadId = std::thread::current().id();
let folder: &ImapFolder = folders let folder: ImapFolder = folders
.lock()
.unwrap()
.values() .values()
.find(|f| f.parent.is_none() && f.path().eq_ignore_ascii_case("INBOX")) .find(|f| f.parent.is_none() && f.path().eq_ignore_ascii_case("INBOX"))
.map(std::clone::Clone::clone)
.unwrap(); .unwrap();
let folder_hash = folder.hash(); let folder_hash = folder.hash();
let mut response = String::with_capacity(8 * 1024); let mut response = String::with_capacity(8 * 1024);
@ -185,7 +204,7 @@ pub fn idle(kit: ImapWatchKit) {
iter.conn.send_raw(b"DONE") iter.conn.send_raw(b"DONE")
iter.conn.read_response(&mut response) iter.conn.read_response(&mut response)
); );
for (hash, folder) in &folders { for (hash, folder) in folders.lock().unwrap().iter() {
if *hash == folder_hash { if *hash == folder_hash {
/* Skip INBOX */ /* Skip INBOX */
continue; continue;

View File

@ -183,6 +183,9 @@ fn move_to_cur(p: PathBuf) -> Result<PathBuf> {
} }
impl MailBackend for MaildirType { impl MailBackend for MaildirType {
fn is_online(&self) -> bool {
true
}
fn folders(&self) -> FnvHashMap<FolderHash, Folder> { fn folders(&self) -> FnvHashMap<FolderHash, Folder> {
self.folders self.folders
.iter() .iter()

View File

@ -367,6 +367,9 @@ pub struct MboxType {
} }
impl MailBackend for MboxType { impl MailBackend for MboxType {
fn is_online(&self) -> bool {
true
}
fn get(&mut self, folder: &Folder) -> Async<Result<Vec<Envelope>>> { fn get(&mut self, folder: &Folder) -> Async<Result<Vec<Envelope>>> {
let mut w = AsyncBuilder::new(); let mut w = AsyncBuilder::new();
let handle = { let handle = {

View File

@ -184,6 +184,10 @@ impl fmt::Display for Listing {
impl Component for Listing { impl Component for Listing {
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
for a in context.accounts.iter_mut() {
a.is_online();
}
if !self.is_dirty() { if !self.is_dirty() {
return; return;
} }
@ -218,7 +222,22 @@ impl Component for Listing {
.push_back(((mid, get_y(upper_left)), (mid, get_y(bottom_right)))); .push_back(((mid, get_y(upper_left)), (mid, get_y(bottom_right))));
} }
self.dirty = false; self.dirty = false;
if right_component_width == total_cols { if right_component_width == total_cols {
if !context.accounts[self.cursor_pos.0].is_online() {
clear_area(grid, area);
write_string_to_grid(
"offline",
grid,
Color::Byte(243),
Color::Default,
Attr::Default,
area,
false,
);
context.dirty_areas.push_back(area);
return;
}
match self.component { match self.component {
Compact(ref mut l) => l.draw(grid, area, context), Compact(ref mut l) => l.draw(grid, area, context),
Plain(ref mut l) => l.draw(grid, area, context), Plain(ref mut l) => l.draw(grid, area, context),
@ -229,6 +248,20 @@ impl Component for Listing {
self.draw_menu(grid, area, context); self.draw_menu(grid, area, context);
} else { } else {
self.draw_menu(grid, (upper_left, (mid, get_y(bottom_right))), context); self.draw_menu(grid, (upper_left, (mid, get_y(bottom_right))), context);
if !context.accounts[self.cursor_pos.0].is_online() {
clear_area(grid, (set_x(upper_left, mid + 1), bottom_right));
write_string_to_grid(
"offline",
grid,
Color::Byte(243),
Color::Default,
Attr::Default,
(set_x(upper_left, mid + 1), bottom_right),
false,
);
context.dirty_areas.push_back(area);
return;
}
match self.component { match self.component {
Compact(ref mut l) => { Compact(ref mut l) => {
l.draw(grid, (set_x(upper_left, mid + 1), bottom_right), context) l.draw(grid, (set_x(upper_left, mid + 1), bottom_right), context)
@ -337,21 +370,26 @@ impl Component for Listing {
} }
_ => return false, _ => return false,
} }
let folder_hash =
context.accounts[self.cursor_pos.0].folders_order[self.cursor_pos.1]; /* Account might have no folders yet if it's offline */
/* Check if per-folder configuration overrides general configuration */ if let Some(&folder_hash) = context.accounts[self.cursor_pos.0]
if let Some(index_style) = context .folders_order
.accounts .get(self.cursor_pos.1)
.get(self.cursor_pos.0)
.and_then(|account| account.folder_confs(folder_hash).conf_override.index_style)
{ {
self.component.set_style(index_style); /* Check if per-folder configuration overrides general configuration */
} else if let Some(index_style) = context if let Some(index_style) =
.accounts context.accounts.get(self.cursor_pos.0).and_then(|account| {
.get(self.cursor_pos.0) account.folder_confs(folder_hash).conf_override.index_style
.and_then(|account| Some(account.settings.conf.index_style())) })
{ {
self.component.set_style(index_style); self.component.set_style(index_style);
} else if let Some(index_style) = context
.accounts
.get(self.cursor_pos.0)
.and_then(|account| Some(account.settings.conf.index_style()))
{
self.component.set_style(index_style);
}
} }
context context
.replies .replies
@ -740,6 +778,15 @@ impl Listing {
); );
if lines.is_empty() { if lines.is_empty() {
write_string_to_grid(
"offline",
grid,
Color::Byte(243),
Color::Default,
Attr::Default,
(pos_inc(upper_left, (0, 1)), bottom_right),
false,
);
return 0; return 0;
} }

View File

@ -25,7 +25,7 @@
use super::{AccountConf, FolderConf}; use super::{AccountConf, FolderConf};
use fnv::FnvHashMap; use fnv::FnvHashMap;
use melib::async_workers::{Async, AsyncBuilder, AsyncStatus}; use melib::async_workers::{Async, AsyncBuilder, AsyncStatus, WorkContext};
use melib::backends::{ use melib::backends::{
BackendOp, Backends, Folder, FolderHash, FolderOperation, MailBackend, NotifyFn, ReadOnlyOp, BackendOp, Backends, Folder, FolderHash, FolderOperation, MailBackend, NotifyFn, ReadOnlyOp,
RefreshEvent, RefreshEventConsumer, RefreshEventKind, SpecialUseMailbox, RefreshEvent, RefreshEventConsumer, RefreshEventKind, SpecialUseMailbox,
@ -118,6 +118,7 @@ impl MailboxEntry {
pub struct Account { pub struct Account {
pub index: usize, pub index: usize,
name: String, name: String,
is_online: bool,
pub(crate) folders: FnvHashMap<FolderHash, MailboxEntry>, pub(crate) folders: FnvHashMap<FolderHash, MailboxEntry>,
pub(crate) folder_confs: FnvHashMap<FolderHash, FolderConf>, pub(crate) folder_confs: FnvHashMap<FolderHash, FolderConf>,
pub(crate) folders_order: Vec<FolderHash>, pub(crate) folders_order: Vec<FolderHash>,
@ -129,6 +130,7 @@ pub struct Account {
pub(crate) address_book: AddressBook, pub(crate) address_book: AddressBook,
pub(crate) workers: FnvHashMap<FolderHash, Worker>, pub(crate) workers: FnvHashMap<FolderHash, Worker>,
pub(crate) work_context: WorkContext,
pub(crate) settings: AccountConf, pub(crate) settings: AccountConf,
pub(crate) runtime_settings: AccountConf, pub(crate) runtime_settings: AccountConf,
@ -199,40 +201,84 @@ impl Account {
name: String, name: String,
settings: AccountConf, settings: AccountConf,
map: &Backends, map: &Backends,
work_context: WorkContext,
notify_fn: NotifyFn, notify_fn: NotifyFn,
) -> Self { ) -> Self {
let s = settings.clone(); let s = settings.clone();
let mut backend = map.get(settings.account().format())( let backend = map.get(settings.account().format())(
settings.account(), settings.account(),
Box::new(move |path: &str| { Box::new(move |path: &str| {
s.folder_confs.contains_key(path) && s.folder_confs[path].subscribe.is_true() s.folder_confs.contains_key(path) && s.folder_confs[path].subscribe.is_true()
}), }),
); );
let mut ref_folders: FnvHashMap<FolderHash, Folder> = backend.folders(); let notify_fn = Arc::new(notify_fn);
let data_dir = xdg::BaseDirectories::with_profile("meli", &name).unwrap();
let address_book = if let Ok(data) = data_dir.place_data_file("addressbook") {
if data.exists() {
let reader = io::BufReader::new(fs::File::open(data).unwrap());
let result: result::Result<AddressBook, _> = serde_json::from_reader(reader);
if let Ok(data_t) = result {
data_t
} else {
AddressBook::new(name.clone())
}
} else {
AddressBook::new(name.clone())
}
} else {
AddressBook::new(name.clone())
};
let mut ret = Account {
index,
name,
is_online: false,
folders: Default::default(),
folder_confs: Default::default(),
folders_order: Default::default(),
folder_names: Default::default(),
tree: Default::default(),
address_book,
sent_folder: Default::default(),
collection: Default::default(),
workers: Default::default(),
work_context,
runtime_settings: settings.clone(),
settings,
backend,
notify_fn,
event_queue: VecDeque::with_capacity(8),
};
ret.is_online();
ret
}
fn init(&mut self) {
let mut ref_folders: FnvHashMap<FolderHash, Folder> = self.backend.folders();
let mut folders: FnvHashMap<FolderHash, MailboxEntry> = let mut folders: FnvHashMap<FolderHash, MailboxEntry> =
FnvHashMap::with_capacity_and_hasher(ref_folders.len(), Default::default()); FnvHashMap::with_capacity_and_hasher(ref_folders.len(), Default::default());
let mut folders_order: Vec<FolderHash> = Vec::with_capacity(ref_folders.len()); let mut folders_order: Vec<FolderHash> = Vec::with_capacity(ref_folders.len());
let mut workers: FnvHashMap<FolderHash, Worker> = FnvHashMap::default(); let mut workers: FnvHashMap<FolderHash, Worker> = FnvHashMap::default();
let notify_fn = Arc::new(notify_fn);
let mut folder_names = FnvHashMap::default(); let mut folder_names = FnvHashMap::default();
let mut folder_confs = FnvHashMap::default(); let mut folder_confs = FnvHashMap::default();
let mut sent_folder = None; let mut sent_folder = None;
for f in ref_folders.values_mut() { for f in ref_folders.values_mut() {
if !settings.folder_confs.contains_key(f.path()) if !self.settings.folder_confs.contains_key(f.path())
|| settings.folder_confs[f.path()].subscribe.is_false() || self.settings.folder_confs[f.path()].subscribe.is_false()
{ {
/* Skip unsubscribed folder */ /* Skip unsubscribed folder */
continue; continue;
} }
match settings.folder_confs[f.path()].usage { match self.settings.folder_confs[f.path()].usage {
Some(SpecialUseMailbox::Sent) => { Some(SpecialUseMailbox::Sent) => {
sent_folder = Some(f.hash()); sent_folder = Some(f.hash());
} }
_ => {} _ => {}
} }
folder_confs.insert(f.hash(), settings.folder_confs[f.path()].clone()); folder_confs.insert(f.hash(), self.settings.folder_confs[f.path()].clone());
folder_names.insert(f.hash(), f.path().to_string()); folder_names.insert(f.hash(), f.path().to_string());
} }
@ -240,8 +286,8 @@ impl Account {
let mut tree: Vec<FolderNode> = Vec::new(); let mut tree: Vec<FolderNode> = Vec::new();
let mut collection: Collection = Collection::new(Default::default()); let mut collection: Collection = Collection::new(Default::default());
for (h, f) in ref_folders.iter() { for (h, f) in ref_folders.iter() {
if !settings.folder_confs.contains_key(f.path()) if !self.settings.folder_confs.contains_key(f.path())
|| settings.folder_confs[f.path()].subscribe.is_false() || self.settings.folder_confs[f.path()].subscribe.is_false()
{ {
/* Skip unsubscribed folder */ /* Skip unsubscribed folder */
continue; continue;
@ -274,7 +320,13 @@ impl Account {
); );
workers.insert( workers.insert(
*h, *h,
Account::new_worker(&settings, f.clone(), &mut backend, notify_fn.clone()), Account::new_worker(
&self.settings,
f.clone(),
&mut self.backend,
&self.work_context,
self.notify_fn.clone(),
),
); );
collection.threads.insert(*h, Threads::default()); collection.threads.insert(*h, Threads::default());
} }
@ -292,47 +344,21 @@ impl Account {
} }
} }
let data_dir = xdg::BaseDirectories::with_profile("meli", &name).unwrap(); self.folders = folders;
let address_book = if let Ok(data) = data_dir.place_data_file("addressbook") { self.folder_confs = folder_confs;
if data.exists() { self.folders_order = folders_order;
let reader = io::BufReader::new(fs::File::open(data).unwrap()); self.folder_names = folder_names;
let result: result::Result<AddressBook, _> = serde_json::from_reader(reader); self.tree = tree;
if let Ok(data_t) = result { self.sent_folder = sent_folder;
data_t self.collection = collection;
} else { self.workers = workers;
AddressBook::new(name.clone())
}
} else {
AddressBook::new(name.clone())
}
} else {
AddressBook::new(name.clone())
};
Account {
index,
name,
folders,
folder_confs,
folders_order,
folder_names,
tree,
address_book,
sent_folder,
collection,
workers,
settings: settings.clone(),
runtime_settings: settings,
backend,
notify_fn,
event_queue: VecDeque::with_capacity(8),
}
} }
fn new_worker( fn new_worker(
settings: &AccountConf, settings: &AccountConf,
folder: Folder, folder: Folder,
backend: &mut Box<dyn MailBackend>, backend: &mut Box<dyn MailBackend>,
work_context: &WorkContext,
notify_fn: Arc<NotifyFn>, notify_fn: Arc<NotifyFn>,
) -> Worker { ) -> Worker {
let mailbox_handle = backend.get(&folder); let mailbox_handle = backend.get(&folder);
@ -361,7 +387,7 @@ impl Account {
* threads' closures this could be avoided, but it requires green threads. * threads' closures this could be avoided, but it requires green threads.
*/ */
builder.set_priority(priority).set_is_static(true); builder.set_priority(priority).set_is_static(true);
let w = builder.build(Box::new(move |work_context| { let mut w = builder.build(Box::new(move |work_context| {
let name = format!("Parsing {}", folder.path()); let name = format!("Parsing {}", folder.path());
let mut mailbox_handle = mailbox_handle.clone(); let mut mailbox_handle = mailbox_handle.clone();
let work = mailbox_handle.work().unwrap(); let work = mailbox_handle.work().unwrap();
@ -397,6 +423,9 @@ impl Account {
} }
} }
})); }));
if let Some(w) = w.work() {
work_context.new_work.send(w).unwrap();
}
Some(w) Some(w)
} }
pub fn reload( pub fn reload(
@ -485,6 +514,7 @@ impl Account {
&self.settings, &self.settings,
ref_folders[&folder_hash].clone(), ref_folders[&folder_hash].clone(),
&mut self.backend, &mut self.backend,
&self.work_context,
self.notify_fn.clone(), self.notify_fn.clone(),
); );
self.workers.insert(folder_hash, handle); self.workers.insert(folder_hash, handle);
@ -758,6 +788,15 @@ impl Account {
.find(|(_, f)| f.usage == Some(special_use)); .find(|(_, f)| f.usage == Some(special_use));
ret.as_ref().map(|r| r.0.as_str()) ret.as_ref().map(|r| r.0.as_str())
} }
pub fn is_online(&mut self) -> bool {
let ret = self.backend.is_online();
if ret != self.is_online && ret {
self.init();
}
self.is_online = ret;
ret
}
} }
impl Index<FolderHash> for Account { impl Index<FolderHash> for Account {

View File

@ -171,6 +171,8 @@ impl State {
let termrows = termsize.map(|(_, h)| h); let termrows = termsize.map(|(_, h)| h);
let cols = termcols.unwrap_or(0) as usize; let cols = termcols.unwrap_or(0) as usize;
let rows = termrows.unwrap_or(0) as usize; let rows = termrows.unwrap_or(0) as usize;
let work_controller = WorkController::new(sender.clone());
let mut accounts: Vec<Account> = settings let mut accounts: Vec<Account> = settings
.accounts .accounts
.iter() .iter()
@ -182,6 +184,7 @@ impl State {
n.to_string(), n.to_string(),
a_s.clone(), a_s.clone(),
&backends, &backends,
work_controller.get_context(),
NotifyFn::new(Box::new(move |f: FolderHash| { NotifyFn::new(Box::new(move |f: FolderHash| {
sender sender
.send(ThreadEvent::UIEvent(UIEvent::StartupCheck(f))) .send(ThreadEvent::UIEvent(UIEvent::StartupCheck(f)))
@ -210,7 +213,7 @@ impl State {
dirty_areas: VecDeque::with_capacity(5), dirty_areas: VecDeque::with_capacity(5),
replies: VecDeque::with_capacity(5), replies: VecDeque::with_capacity(5),
temp_files: Vec::new(), temp_files: Vec::new(),
work_controller: WorkController::new(sender.clone()), work_controller,
sender, sender,
receiver, receiver,
@ -225,16 +228,6 @@ impl State {
s.grid.set_ascii_drawing(true); s.grid.set_ascii_drawing(true);
} }
for a in s.context.accounts.iter_mut() {
for worker in a.workers.values_mut() {
if let Some(worker) = worker.as_mut() {
if let Some(w) = worker.work() {
s.context.work_controller.queue.add_work(w);
}
}
}
}
s.switch_to_alternate_screen(); s.switch_to_alternate_screen();
debug!("inserting mailbox hashes:"); debug!("inserting mailbox hashes:");
{ {