348 lines
10 KiB
Rust
348 lines
10 KiB
Rust
//
|
|
// meli - accounts module.
|
|
//
|
|
// Copyright 2017 Emmanouil Pitsidianakis <manos@pitsidianak.is>
|
|
//
|
|
// This file is part of meli.
|
|
//
|
|
// meli is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// meli is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with meli. If not, see <http://www.gnu.org/licenses/>.
|
|
//
|
|
// SPDX-License-Identifier: EUPL-1.2 OR GPL-3.0-or-later
|
|
|
|
use indexmap::IndexMap;
|
|
use melib::{
|
|
backends::{Mailbox, MailboxHash},
|
|
error::Error,
|
|
log,
|
|
};
|
|
use smallvec::SmallVec;
|
|
|
|
use crate::{conf::FileMailboxConf, is_variant};
|
|
|
|
#[derive(Clone, Debug, Default)]
|
|
pub enum MailboxStatus {
|
|
Available,
|
|
Failed(Error),
|
|
/// first argument is done work, and second is total work
|
|
Parsing(usize, usize),
|
|
#[default]
|
|
None,
|
|
}
|
|
|
|
impl MailboxStatus {
|
|
is_variant! { is_available, Available }
|
|
is_variant! { is_parsing, Parsing(_, _) }
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct MailboxEntry {
|
|
pub status: MailboxStatus,
|
|
pub name: String,
|
|
pub path: String,
|
|
pub ref_mailbox: Mailbox,
|
|
pub conf: FileMailboxConf,
|
|
}
|
|
|
|
impl MailboxEntry {
|
|
pub fn new(
|
|
status: MailboxStatus,
|
|
name: String,
|
|
ref_mailbox: Mailbox,
|
|
conf: FileMailboxConf,
|
|
) -> Self {
|
|
let mut ret = Self {
|
|
status,
|
|
name,
|
|
path: ref_mailbox.path().into(),
|
|
ref_mailbox,
|
|
conf,
|
|
};
|
|
match ret.conf.mailbox_conf.extra.get("encoding") {
|
|
None => {}
|
|
Some(v) if ["utf-8", "utf8"].iter().any(|e| v.eq_ignore_ascii_case(e)) => {}
|
|
Some(v) if ["utf-7", "utf7"].iter().any(|e| v.eq_ignore_ascii_case(e)) => {
|
|
ret.name = melib::backends::utf7::decode_utf7_imap(&ret.name);
|
|
ret.path = melib::backends::utf7::decode_utf7_imap(&ret.path);
|
|
}
|
|
Some(other) => {
|
|
log::warn!(
|
|
"mailbox `{}`: unrecognized mailbox name charset: {}",
|
|
&ret.name,
|
|
other
|
|
);
|
|
}
|
|
}
|
|
ret
|
|
}
|
|
|
|
pub fn status(&self) -> String {
|
|
match self.status {
|
|
MailboxStatus::Available => format!(
|
|
"{} [{} messages]",
|
|
self.name(),
|
|
self.ref_mailbox.count().ok().unwrap_or((0, 0)).1
|
|
),
|
|
MailboxStatus::Failed(ref e) => e.to_string(),
|
|
MailboxStatus::None => "Retrieving mailbox.".to_string(),
|
|
MailboxStatus::Parsing(done, total) => {
|
|
format!("Parsing messages. [{}/{}]", done, total)
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn name(&self) -> &str {
|
|
if let Some(name) = self.conf.mailbox_conf.alias.as_ref() {
|
|
name
|
|
} else {
|
|
self.ref_mailbox.name()
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, Default, Serialize)]
|
|
pub struct MailboxNode {
|
|
pub hash: MailboxHash,
|
|
pub depth: usize,
|
|
pub indentation: u32,
|
|
pub has_sibling: bool,
|
|
pub children: Vec<MailboxNode>,
|
|
}
|
|
|
|
pub fn build_mailboxes_order(
|
|
tree: &mut Vec<MailboxNode>,
|
|
mailbox_entries: &IndexMap<MailboxHash, MailboxEntry>,
|
|
mailboxes_order: &mut Vec<MailboxHash>,
|
|
) {
|
|
tree.clear();
|
|
mailboxes_order.clear();
|
|
for (h, f) in mailbox_entries.iter() {
|
|
if f.ref_mailbox.parent().is_none() {
|
|
fn rec(
|
|
h: MailboxHash,
|
|
mailbox_entries: &IndexMap<MailboxHash, MailboxEntry>,
|
|
depth: usize,
|
|
) -> MailboxNode {
|
|
let mut node = MailboxNode {
|
|
hash: h,
|
|
children: Vec::new(),
|
|
depth,
|
|
indentation: 0,
|
|
has_sibling: false,
|
|
};
|
|
for &c in mailbox_entries[&h].ref_mailbox.children() {
|
|
if mailbox_entries.contains_key(&c) {
|
|
node.children.push(rec(c, mailbox_entries, depth + 1));
|
|
}
|
|
}
|
|
node
|
|
}
|
|
|
|
tree.push(rec(*h, mailbox_entries, 0));
|
|
}
|
|
}
|
|
|
|
macro_rules! mailbox_eq_key {
|
|
($mailbox:expr) => {{
|
|
if let Some(sort_order) = $mailbox.conf.mailbox_conf.sort_order {
|
|
(0, sort_order, $mailbox.ref_mailbox.path())
|
|
} else {
|
|
(1, 0, $mailbox.ref_mailbox.path())
|
|
}
|
|
}};
|
|
}
|
|
tree.sort_unstable_by(|a, b| {
|
|
if mailbox_entries[&b.hash]
|
|
.conf
|
|
.mailbox_conf
|
|
.sort_order
|
|
.is_none()
|
|
&& mailbox_entries[&b.hash]
|
|
.ref_mailbox
|
|
.path()
|
|
.eq_ignore_ascii_case("INBOX")
|
|
{
|
|
std::cmp::Ordering::Greater
|
|
} else if mailbox_entries[&a.hash]
|
|
.conf
|
|
.mailbox_conf
|
|
.sort_order
|
|
.is_none()
|
|
&& mailbox_entries[&a.hash]
|
|
.ref_mailbox
|
|
.path()
|
|
.eq_ignore_ascii_case("INBOX")
|
|
{
|
|
std::cmp::Ordering::Less
|
|
} else {
|
|
mailbox_eq_key!(mailbox_entries[&a.hash])
|
|
.cmp(&mailbox_eq_key!(mailbox_entries[&b.hash]))
|
|
}
|
|
});
|
|
|
|
let mut stack: SmallVec<[Option<&MailboxNode>; 16]> = SmallVec::new();
|
|
for n in tree.iter_mut() {
|
|
mailboxes_order.push(n.hash);
|
|
n.children.sort_unstable_by(|a, b| {
|
|
if mailbox_entries[&b.hash]
|
|
.conf
|
|
.mailbox_conf
|
|
.sort_order
|
|
.is_none()
|
|
&& mailbox_entries[&b.hash]
|
|
.ref_mailbox
|
|
.path()
|
|
.eq_ignore_ascii_case("INBOX")
|
|
{
|
|
std::cmp::Ordering::Greater
|
|
} else if mailbox_entries[&a.hash]
|
|
.conf
|
|
.mailbox_conf
|
|
.sort_order
|
|
.is_none()
|
|
&& mailbox_entries[&a.hash]
|
|
.ref_mailbox
|
|
.path()
|
|
.eq_ignore_ascii_case("INBOX")
|
|
{
|
|
std::cmp::Ordering::Less
|
|
} else {
|
|
mailbox_eq_key!(mailbox_entries[&a.hash])
|
|
.cmp(&mailbox_eq_key!(mailbox_entries[&b.hash]))
|
|
}
|
|
});
|
|
stack.extend(n.children.iter().rev().map(Some));
|
|
while let Some(Some(next)) = stack.pop() {
|
|
mailboxes_order.push(next.hash);
|
|
stack.extend(next.children.iter().rev().map(Some));
|
|
}
|
|
}
|
|
drop(stack);
|
|
for node in tree.iter_mut() {
|
|
fn rec(
|
|
node: &mut MailboxNode,
|
|
mailbox_entries: &IndexMap<MailboxHash, MailboxEntry>,
|
|
mut indentation: u32,
|
|
has_sibling: bool,
|
|
) {
|
|
node.indentation = indentation;
|
|
node.has_sibling = has_sibling;
|
|
let mut iter = (0..node.children.len())
|
|
.filter(|i| {
|
|
mailbox_entries[&node.children[*i].hash]
|
|
.ref_mailbox
|
|
.is_subscribed()
|
|
})
|
|
.collect::<SmallVec<[_; 8]>>()
|
|
.into_iter()
|
|
.peekable();
|
|
indentation <<= 1;
|
|
if has_sibling {
|
|
indentation |= 1;
|
|
}
|
|
while let Some(i) = iter.next() {
|
|
let c = &mut node.children[i];
|
|
rec(c, mailbox_entries, indentation, iter.peek().is_some());
|
|
}
|
|
}
|
|
|
|
rec(node, mailbox_entries, 0, false);
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use melib::{
|
|
backends::{Mailbox, MailboxHash},
|
|
error::Result,
|
|
MailboxPermissions, SpecialUsageMailbox,
|
|
};
|
|
|
|
use crate::accounts::{FileMailboxConf, MailboxEntry, MailboxStatus};
|
|
|
|
#[test]
|
|
fn test_mailbox_utf7() {
|
|
#[derive(Debug)]
|
|
struct TestMailbox(String);
|
|
|
|
impl melib::BackendMailbox for TestMailbox {
|
|
fn hash(&self) -> MailboxHash {
|
|
unimplemented!()
|
|
}
|
|
|
|
fn name(&self) -> &str {
|
|
&self.0
|
|
}
|
|
|
|
fn path(&self) -> &str {
|
|
&self.0
|
|
}
|
|
|
|
fn children(&self) -> &[MailboxHash] {
|
|
unimplemented!()
|
|
}
|
|
|
|
fn clone(&self) -> Mailbox {
|
|
unimplemented!()
|
|
}
|
|
|
|
fn special_usage(&self) -> SpecialUsageMailbox {
|
|
unimplemented!()
|
|
}
|
|
|
|
fn parent(&self) -> Option<MailboxHash> {
|
|
unimplemented!()
|
|
}
|
|
|
|
fn permissions(&self) -> MailboxPermissions {
|
|
unimplemented!()
|
|
}
|
|
|
|
fn is_subscribed(&self) -> bool {
|
|
unimplemented!()
|
|
}
|
|
|
|
fn set_is_subscribed(&mut self, _: bool) -> Result<()> {
|
|
unimplemented!()
|
|
}
|
|
|
|
fn set_special_usage(&mut self, _: SpecialUsageMailbox) -> Result<()> {
|
|
unimplemented!()
|
|
}
|
|
|
|
fn count(&self) -> Result<(usize, usize)> {
|
|
unimplemented!()
|
|
}
|
|
}
|
|
for (n, d) in [
|
|
("~peter/mail/&U,BTFw-/&ZeVnLIqe-", "~peter/mail/台北/日本語"),
|
|
("&BB4EQgQ,BEAEMAQyBDsENQQ9BD0ESwQ1-", "Отправленные"),
|
|
] {
|
|
let ref_mbox = TestMailbox(n.to_string());
|
|
let mut conf: melib::MailboxConf = Default::default();
|
|
conf.extra.insert("encoding".to_string(), "utf7".into());
|
|
|
|
let entry = MailboxEntry::new(
|
|
MailboxStatus::None,
|
|
n.to_string(),
|
|
Box::new(ref_mbox),
|
|
FileMailboxConf {
|
|
mailbox_conf: conf,
|
|
..Default::default()
|
|
},
|
|
);
|
|
assert_eq!(&entry.path, d);
|
|
}
|
|
}
|
|
}
|