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 {
fn hash(&self) -> FolderHash;
fn name(&self) -> &str;
fn path(&self) -> &str {
self.name()
}
/// Path of folder within the mailbox hierarchy, with `/` as separator.
fn path(&self) -> &str;
fn change_name(&mut self, new_name: &str);
fn clone(&self) -> Folder;
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 clone(&self) -> Folder {

View File

@ -177,6 +177,7 @@ impl<'a> BackendOp for MaildirOp {
pub struct MaildirFolder {
hash: FolderHash,
name: String,
fs_path: PathBuf,
path: PathBuf,
parent: Option<FolderHash>,
children: Vec<FolderHash>,
@ -188,26 +189,62 @@ impl MaildirFolder {
file_name: String,
parent: Option<FolderHash>,
children: Vec<FolderHash>,
settings: &AccountSettings,
) -> 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();
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 {
hash: h.finish(),
name: file_name,
path: pathbuf,
path: fname.unwrap().to_path_buf(),
fs_path: pathbuf,
parent,
children,
};
ret.is_valid()?;
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<()> {
let path = self.path();
let path = self.fs_path();
let mut p = PathBuf::from(path);
for d in &["cur", "new", "tmp"] {
p.push(d);
@ -231,6 +268,10 @@ impl BackendFolder for MaildirFolder {
&self.name
}
fn path(&self) -> &str {
self.path.to_str().unwrap_or(self.name())
}
fn change_name(&mut self, s: &str) {
self.name = s.to_string();
}
@ -243,6 +284,7 @@ impl BackendFolder for MaildirFolder {
Box::new(MaildirFolder {
hash: self.hash,
name: self.name.clone(),
fs_path: self.fs_path.clone(),
path: self.path.clone(),
children: self.children.clone(),
parent: self.parent,

View File

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

View File

@ -28,6 +28,7 @@ pub struct AccountSettings {
pub identity: String,
pub read_only: bool,
pub display_name: Option<String>,
pub subscribed_folders: Vec<String>,
}
impl AccountSettings {
@ -52,4 +53,8 @@ impl AccountSettings {
pub fn display_name(&self) -> Option<&String> {
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,
#[serde(deserialize_with = "toggleflag_de", default)]
subscribe: ToggleFlag,
#[serde(deserialize_with = "toggleflag_de", default)]
ignore: ToggleFlag,
}
impl Default for FolderConf {
@ -103,6 +105,7 @@ impl Default for FolderConf {
rename: None,
autoload: true,
subscribe: ToggleFlag::Unset,
ignore: ToggleFlag::False,
}
}
}
@ -133,6 +136,7 @@ pub struct FileAccount {
#[serde(default = "false_val")]
read_only: bool,
subscribed_folders: Vec<String>,
folders: Option<HashMap<String, FolderConf>>,
}
@ -144,7 +148,7 @@ impl From<FileAccount> for AccountConf {
let identity = x.identity.clone();
let display_name = x.display_name.clone();
let acc = AccountSettings {
let mut acc = AccountSettings {
name: String::new(),
root_folder,
format,
@ -152,9 +156,39 @@ impl From<FileAccount> for AccountConf {
identity,
read_only: x.read_only,
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 {
account: acc,
@ -384,7 +418,7 @@ mod default_vals {
80
}
pub(in crate::conf) fn none() -> Option<String> {
pub(in crate::conf) fn none<T>() -> Option<T> {
None
}
}

View File

@ -206,27 +206,24 @@ impl Account {
let mut sent_folder = None;
for f in ref_folders.values_mut() {
let entry = settings
.folder_confs
.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()
if !settings.folder_confs.contains_key(f.path())
|| settings.folder_confs[f.path()].subscribe.is_false()
{
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 tree: Vec<FolderNode> = Vec::new();
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() {
fn rec(h: FolderHash, ref_folders: &FnvHashMap<FolderHash, Folder>) -> FolderNode {
let mut node = FolderNode {
@ -255,12 +252,13 @@ impl Account {
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();
for n in tree.iter_mut() {
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));
while let Some(Some(next)) = stack.pop() {
folders_order.push(next.hash);
@ -422,7 +420,7 @@ impl Account {
if let Some(folder_confs) = self.settings.conf().folders() {
//debug!("folder renames: {:?}", folder_renames);
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() {
f.change_name(rename);
}