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