add folder subscriptions
parent
b39b285711
commit
76909a1959
|
@ -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 {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue