add folder subscriptions

embed
Manos Pitsidianakis 2019-08-23 21:32:32 +03:00
parent b39b285711
commit 76909a1959
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
6 changed files with 153 additions and 48 deletions

View File

@ -248,9 +248,8 @@ impl BackendOp for ReadOnlyOp {
pub trait BackendFolder: Debug { pub trait BackendFolder: Debug {
fn hash(&self) -> FolderHash; fn hash(&self) -> FolderHash;
fn name(&self) -> &str; fn name(&self) -> &str;
fn path(&self) -> &str { /// Path of folder within the mailbox hierarchy, with `/` as separator.
self.name() fn path(&self) -> &str;
}
fn change_name(&mut self, new_name: &str); fn change_name(&mut self, new_name: &str);
fn clone(&self) -> Folder; fn clone(&self) -> Folder;
fn children(&self) -> &Vec<FolderHash>; fn children(&self) -> &Vec<FolderHash>;
@ -271,6 +270,10 @@ impl BackendFolder for DummyFolder {
"" ""
} }
fn path(&self) -> &str {
""
}
fn change_name(&mut self, _s: &str) {} fn change_name(&mut self, _s: &str) {}
fn clone(&self) -> Folder { fn clone(&self) -> Folder {

View File

@ -177,6 +177,7 @@ impl<'a> BackendOp for MaildirOp {
pub struct MaildirFolder { pub struct MaildirFolder {
hash: FolderHash, hash: FolderHash,
name: String, name: String,
fs_path: PathBuf,
path: PathBuf, path: PathBuf,
parent: Option<FolderHash>, parent: Option<FolderHash>,
children: Vec<FolderHash>, children: Vec<FolderHash>,
@ -188,26 +189,62 @@ impl MaildirFolder {
file_name: String, file_name: String,
parent: Option<FolderHash>, parent: Option<FolderHash>,
children: Vec<FolderHash>, children: Vec<FolderHash>,
settings: &AccountSettings,
) -> Result<Self> { ) -> Result<Self> {
let pathbuf = PathBuf::from(path); macro_rules! strip_slash {
($v:expr) => {
if $v.ends_with("/") {
&$v[..$v.len() - 1]
} else {
$v
}
};
}
let pathbuf = PathBuf::from(&path);
let mut h = DefaultHasher::new(); let mut h = DefaultHasher::new();
pathbuf.hash(&mut h); pathbuf.hash(&mut h);
/* Check if folder path (Eg `INBOX/Lists/luddites`) is included in the subscribed
* mailboxes in user configuration */
let fname = if let Ok(fname) = pathbuf.strip_prefix(
PathBuf::from(&settings.root_folder)
.parent()
.unwrap_or_else(|| &Path::new("/")),
) {
if fname.components().count() != 0
&& !settings
.subscribed_folders
.iter()
.any(|x| x == strip_slash!(fname.to_str().unwrap()))
{
return Err(MeliError::new(format!(
"Folder with name `{}` is not included in configured subscribed mailboxes",
fname.display()
)));
}
Some(fname)
} else {
None
};
let ret = MaildirFolder { let ret = MaildirFolder {
hash: h.finish(), hash: h.finish(),
name: file_name, name: file_name,
path: pathbuf, path: fname.unwrap().to_path_buf(),
fs_path: pathbuf,
parent, parent,
children, children,
}; };
ret.is_valid()?; ret.is_valid()?;
Ok(ret) Ok(ret)
} }
pub fn path(&self) -> &Path {
self.path.as_path() pub fn fs_path(&self) -> &Path {
self.fs_path.as_path()
} }
fn is_valid(&self) -> Result<()> { fn is_valid(&self) -> Result<()> {
let path = self.path(); let path = self.fs_path();
let mut p = PathBuf::from(path); let mut p = PathBuf::from(path);
for d in &["cur", "new", "tmp"] { for d in &["cur", "new", "tmp"] {
p.push(d); p.push(d);
@ -231,6 +268,10 @@ impl BackendFolder for MaildirFolder {
&self.name &self.name
} }
fn path(&self) -> &str {
self.path.to_str().unwrap_or(self.name())
}
fn change_name(&mut self, s: &str) { fn change_name(&mut self, s: &str) {
self.name = s.to_string(); self.name = s.to_string();
} }
@ -243,6 +284,7 @@ impl BackendFolder for MaildirFolder {
Box::new(MaildirFolder { Box::new(MaildirFolder {
hash: self.hash, hash: self.hash,
name: self.name.clone(), name: self.name.clone(),
fs_path: self.fs_path.clone(),
path: self.path.clone(), path: self.path.clone(),
children: self.children.clone(), children: self.children.clone(),
parent: self.parent, parent: self.parent,

View File

@ -467,7 +467,7 @@ impl MailBackend for MaildirType {
fn save(&self, bytes: &[u8], folder: &str) -> Result<()> { fn save(&self, bytes: &[u8], folder: &str) -> Result<()> {
for f in self.folders.values() { for f in self.folders.values() {
if f.name == folder { if f.name == folder {
let mut path = f.path.clone(); let mut path = f.fs_path.clone();
path.push("cur"); path.push("cur");
{ {
let mut rand_buf = [0u8; 16]; let mut rand_buf = [0u8; 16];
@ -508,10 +508,11 @@ impl MailBackend for MaildirType {
} }
impl MaildirType { impl MaildirType {
pub fn new(f: &AccountSettings) -> Self { pub fn new(settings: &AccountSettings) -> Self {
let mut folders: FnvHashMap<FolderHash, MaildirFolder> = Default::default(); let mut folders: FnvHashMap<FolderHash, MaildirFolder> = Default::default();
fn recurse_folders<P: AsRef<Path>>( fn recurse_folders<P: AsRef<Path>>(
folders: &mut FnvHashMap<FolderHash, MaildirFolder>, folders: &mut FnvHashMap<FolderHash, MaildirFolder>,
settings: &AccountSettings,
p: P, p: P,
) -> Vec<FolderHash> { ) -> Vec<FolderHash> {
if !p.as_ref().exists() || !p.as_ref().is_dir() { if !p.as_ref().exists() || !p.as_ref().is_dir() {
@ -535,13 +536,14 @@ impl MaildirType {
continue 'entries; continue 'entries;
} }
if path.is_dir() { if path.is_dir() {
let path_children = recurse_folders(folders, &path); if let Ok(mut f) = MaildirFolder::new(
if let Ok(f) = MaildirFolder::new(
path.to_str().unwrap().to_string(), path.to_str().unwrap().to_string(),
path.file_name().unwrap().to_str().unwrap().to_string(), path.file_name().unwrap().to_str().unwrap().to_string(),
None, None,
path_children, Vec::new(),
&settings,
) { ) {
f.children = recurse_folders(folders, settings, &path);
f.children f.children
.iter() .iter()
.map(|c| folders.get_mut(c).map(|f| f.parent = Some(f.hash))) .map(|c| folders.get_mut(c).map(|f| f.parent = Some(f.hash)))
@ -555,28 +557,49 @@ impl MaildirType {
} }
children children
}; };
let path = PathBuf::from(f.root_folder()); let root_path = PathBuf::from(settings.root_folder());
if path.is_dir() { if !root_path.exists() {
if let Ok(f) = MaildirFolder::new( eprintln!(
path.to_str().unwrap().to_string(), "Configuration error ({}): root_path `{}` is not a valid directory.",
path.file_name().unwrap().to_str().unwrap().to_string(), settings.name(),
None, settings.root_folder.as_str()
Vec::with_capacity(0), );
) { std::process::exit(1);
let l: MaildirFolder = f; } else if !root_path.is_dir() {
folders.insert(l.hash, l); eprintln!(
"Configuration error ({}): root_path `{}` is not a directory.",
settings.name(),
settings.root_folder.as_str()
);
std::process::exit(1);
}
match MaildirFolder::new(
root_path.to_str().unwrap().to_string(),
root_path.file_name().unwrap().to_str().unwrap().to_string(),
None,
Vec::with_capacity(0),
settings,
) {
Ok(f) => {
folders.insert(f.hash, f);
}
Err(e) => {
eprint!("{}: ", settings.name());
eprintln!("{}", e.to_string());
std::process::exit(1);
} }
} }
if folders.is_empty() { if folders.is_empty() {
let children = recurse_folders(&mut folders, &path); let children = recurse_folders(&mut folders, settings, &root_path);
children children
.iter() .iter()
.map(|c| folders.get_mut(c).map(|f| f.parent = None)) .map(|c| folders.get_mut(c).map(|f| f.parent = None))
.count(); .count();
} else { } else {
let root_hash = *folders.keys().nth(0).unwrap(); let root_hash = *folders.keys().nth(0).unwrap();
let children = recurse_folders(&mut folders, &path); let children = recurse_folders(&mut folders, settings, &root_path);
children children
.iter() .iter()
.map(|c| folders.get_mut(c).map(|f| f.parent = Some(root_hash))) .map(|c| folders.get_mut(c).map(|f| f.parent = Some(root_hash)))
@ -601,10 +624,10 @@ impl MaildirType {
} }
} }
MaildirType { MaildirType {
name: f.name().to_string(), name: settings.name().to_string(),
folders, folders,
hash_indexes, hash_indexes,
path: PathBuf::from(f.root_folder()), path: PathBuf::from(settings.root_folder()),
} }
} }
fn owned_folder_idx(&self, folder: &Folder) -> FolderHash { fn owned_folder_idx(&self, folder: &Folder) -> FolderHash {
@ -626,7 +649,7 @@ impl MaildirType {
let folder: &MaildirFolder = &self.folders[&self.owned_folder_idx(folder)]; let folder: &MaildirFolder = &self.folders[&self.owned_folder_idx(folder)];
let folder_hash = folder.hash(); let folder_hash = folder.hash();
let tx_final = w.tx(); let tx_final = w.tx();
let path: PathBuf = folder.path().into(); let path: PathBuf = folder.fs_path().into();
let name = format!("parsing {:?}", folder.name()); let name = format!("parsing {:?}", folder.name());
let root_path = self.path.to_path_buf(); let root_path = self.path.to_path_buf();
let map = self.hash_indexes.clone(); let map = self.hash_indexes.clone();

View File

@ -28,6 +28,7 @@ pub struct AccountSettings {
pub identity: String, pub identity: String,
pub read_only: bool, pub read_only: bool,
pub display_name: Option<String>, pub display_name: Option<String>,
pub subscribed_folders: Vec<String>,
} }
impl AccountSettings { impl AccountSettings {
@ -52,4 +53,8 @@ impl AccountSettings {
pub fn display_name(&self) -> Option<&String> { pub fn display_name(&self) -> Option<&String> {
self.display_name.as_ref() self.display_name.as_ref()
} }
pub fn subscribed_folders(&self) -> &Vec<String> {
&self.subscribed_folders
}
} }

View File

@ -95,6 +95,8 @@ pub struct FolderConf {
autoload: bool, autoload: bool,
#[serde(deserialize_with = "toggleflag_de", default)] #[serde(deserialize_with = "toggleflag_de", default)]
subscribe: ToggleFlag, subscribe: ToggleFlag,
#[serde(deserialize_with = "toggleflag_de", default)]
ignore: ToggleFlag,
} }
impl Default for FolderConf { impl Default for FolderConf {
@ -103,6 +105,7 @@ impl Default for FolderConf {
rename: None, rename: None,
autoload: true, autoload: true,
subscribe: ToggleFlag::Unset, subscribe: ToggleFlag::Unset,
ignore: ToggleFlag::False,
} }
} }
} }
@ -133,6 +136,7 @@ pub struct FileAccount {
#[serde(default = "false_val")] #[serde(default = "false_val")]
read_only: bool, read_only: bool,
subscribed_folders: Vec<String>,
folders: Option<HashMap<String, FolderConf>>, folders: Option<HashMap<String, FolderConf>>,
} }
@ -144,7 +148,7 @@ impl From<FileAccount> for AccountConf {
let identity = x.identity.clone(); let identity = x.identity.clone();
let display_name = x.display_name.clone(); let display_name = x.display_name.clone();
let acc = AccountSettings { let mut acc = AccountSettings {
name: String::new(), name: String::new(),
root_folder, root_folder,
format, format,
@ -152,9 +156,39 @@ impl From<FileAccount> for AccountConf {
identity, identity,
read_only: x.read_only, read_only: x.read_only,
display_name, display_name,
subscribed_folders: x.subscribed_folders.clone(),
}; };
let folder_confs = x.folders.clone().unwrap_or_else(Default::default); let root_path = PathBuf::from(acc.root_folder.as_str());
let root_tmp = root_path
.components()
.last()
.unwrap()
.as_os_str()
.to_str()
.unwrap()
.to_string();
if !acc.subscribed_folders.contains(&root_tmp) {
acc.subscribed_folders.push(root_tmp);
}
let mut folder_confs = x.folders.clone().unwrap_or_else(Default::default);
for s in &x.subscribed_folders {
if !folder_confs.contains_key(s) {
folder_confs.insert(
s.to_string(),
FolderConf {
subscribe: ToggleFlag::True,
..FolderConf::default()
},
);
} else {
if !folder_confs[s].subscribe.is_unset() {
eprintln!("Configuration error: folder `{}` cannot both have `subscribe` flag set and be in the `subscribed_folders` array", s);
std::process::exit(1);
}
folder_confs.get_mut(s).unwrap().subscribe = ToggleFlag::True;
}
}
AccountConf { AccountConf {
account: acc, account: acc,
@ -384,7 +418,7 @@ mod default_vals {
80 80
} }
pub(in crate::conf) fn none() -> Option<String> { pub(in crate::conf) fn none<T>() -> Option<T> {
None None
} }
} }

View File

@ -206,27 +206,24 @@ impl Account {
let mut sent_folder = None; let mut sent_folder = None;
for f in ref_folders.values_mut() { for f in ref_folders.values_mut() {
let entry = settings if !settings.folder_confs.contains_key(f.path())
.folder_confs || settings.folder_confs[f.path()].subscribe.is_false()
.entry(f.name().to_string())
.or_default();
if f.name().eq_ignore_ascii_case("sent") {
sent_folder = Some(f.hash());
}
if (f.name().eq_ignore_ascii_case("junk")
|| f.name().eq_ignore_ascii_case("spam")
|| f.name().eq_ignore_ascii_case("sent")
|| f.name().eq_ignore_ascii_case("trash"))
&& entry.subscribe.is_unset()
{ {
entry.subscribe = ToggleFlag::InternalVal(false); /* Skip unsubscribed folder */
continue;
} }
folder_names.insert(f.hash(), f.name().to_string()); folder_names.insert(f.hash(), f.path().to_string());
} }
let mut stack: StackVec<FolderHash> = StackVec::new(); let mut stack: StackVec<FolderHash> = StackVec::new();
let mut tree: Vec<FolderNode> = Vec::new(); let mut tree: Vec<FolderNode> = Vec::new();
for (h, f) in ref_folders.iter() { for (h, f) in ref_folders.iter() {
if !settings.folder_confs.contains_key(f.path())
|| settings.folder_confs[f.path()].subscribe.is_false()
{
/* Skip unsubscribed folder */
continue;
}
if f.parent().is_none() { if f.parent().is_none() {
fn rec(h: FolderHash, ref_folders: &FnvHashMap<FolderHash, Folder>) -> FolderNode { fn rec(h: FolderHash, ref_folders: &FnvHashMap<FolderHash, Folder>) -> FolderNode {
let mut node = FolderNode { let mut node = FolderNode {
@ -255,12 +252,13 @@ impl Account {
Account::new_worker(f.clone(), &mut backend, notify_fn.clone()), Account::new_worker(f.clone(), &mut backend, notify_fn.clone()),
); );
} }
tree.sort_unstable_by_key(|f| ref_folders[&f.hash].name());
tree.sort_unstable_by_key(|f| ref_folders[&f.hash].path());
let mut stack: StackVec<Option<&FolderNode>> = StackVec::new(); let mut stack: StackVec<Option<&FolderNode>> = StackVec::new();
for n in tree.iter_mut() { for n in tree.iter_mut() {
folders_order.push(n.hash); folders_order.push(n.hash);
n.kids.sort_unstable_by_key(|f| ref_folders[&f.hash].name()); n.kids.sort_unstable_by_key(|f| ref_folders[&f.hash].path());
stack.extend(n.kids.iter().rev().map(Some)); stack.extend(n.kids.iter().rev().map(Some));
while let Some(Some(next)) = stack.pop() { while let Some(Some(next)) = stack.pop() {
folders_order.push(next.hash); folders_order.push(next.hash);
@ -422,7 +420,7 @@ impl Account {
if let Some(folder_confs) = self.settings.conf().folders() { if let Some(folder_confs) = self.settings.conf().folders() {
//debug!("folder renames: {:?}", folder_renames); //debug!("folder renames: {:?}", folder_renames);
for f in folders.values_mut() { for f in folders.values_mut() {
if let Some(r) = folder_confs.get(f.name()) { if let Some(r) = folder_confs.get(f.path()) {
if let Some(rename) = r.rename() { if let Some(rename) = r.rename() {
f.change_name(rename); f.change_name(rename);
} }