use std::borrow::Cow;
use std::collections::HashSet;
use std::path::PathBuf;
use std::sync::Arc;
use chrono::prelude::*;
use super::super::storage;
use crate::{
account::{key_store::KeyStore, model::*},
crypt::master_key::MasterKey,
support::{error::Error, log_prefix::LogPrefix, small_bitset::SmallBitset},
};
pub(super) const METADB_NAME: &str = "meta.sqlite.xex";
pub(super) const DELIVERYDB_NAME: &str = "delivery.sqlite";
pub struct Account {
pub(super) master_key: Arc<MasterKey>,
pub(super) metadb: storage::MetaDb,
pub(super) metadb_path: PathBuf,
pub(super) deliverydb: storage::DeliveryDb,
pub(super) deliverydb_path: PathBuf,
pub(super) message_store: storage::MessageStore,
pub(super) key_store: KeyStore,
pub(super) root: PathBuf,
pub(super) common_paths: Arc<CommonPaths>,
pub(super) backup_path: PathBuf,
pub(super) log_prefix: LogPrefix,
}
#[derive(Clone, Debug)]
pub struct Mailbox {
pub(super) id: storage::MailboxId,
pub(super) writable: bool,
pub(super) messages: Vec<MessageStatus>,
pub(super) flags: Vec<(storage::FlagId, Flag)>,
pub(super) max_client_known_flag_id: storage::FlagId,
pub(super) snapshot_modseq: Modseq,
pub(super) polled_snapshot_modseq: Modseq,
pub(super) has_pending_expunge: bool,
pub(super) next_uid: Uid,
pub(super) changed_flags_uids: Vec<Uid>,
pub(super) fetch_loopbreaker: HashSet<Uid>,
}
#[derive(Clone, Debug)]
pub(super) struct MessageStatus {
pub(super) uid: Uid,
pub(super) id: storage::MessageId,
pub(super) flags: SmallBitset,
pub(super) last_modified: Modseq,
pub(super) recent: bool,
pub(super) savedate: DateTime<Utc>,
}
impl From<storage::InitialMessageStatus> for MessageStatus {
fn from(m: storage::InitialMessageStatus) -> Self {
Self {
uid: m.uid,
id: m.id,
flags: m.flags,
last_modified: m.last_modified,
recent: m.recent,
savedate: m.savedate.0,
}
}
}
impl Account {
pub fn config_file(&self) -> PathBuf {
self.root.join("user.toml")
}
pub fn common_paths(&self) -> Arc<CommonPaths> {
Arc::clone(&self.common_paths)
}
pub fn user_name(&self) -> Option<&str> {
self.root.file_name().and_then(|name| name.to_str())
}
}
impl Mailbox {
pub fn snapshot_modseq(&self) -> Modseq {
self.snapshot_modseq
}
pub fn has_pending_expunge(&self) -> bool {
self.has_pending_expunge
}
pub fn max_seqnum(&self) -> Seqnum {
Seqnum::from_index(self.messages.len().saturating_sub(1))
}
pub fn next_uid(&self) -> Uid {
self.next_uid
}
pub fn read_only(&self) -> bool {
!self.writable
}
pub fn rfc8474_mailbox_id(&self) -> String {
self.id.format_rfc8474()
}
pub fn add_changed_uids(&mut self, uids: impl Iterator<Item = Uid>) {
self.changed_flags_uids.extend(uids);
}
pub(super) fn require_writable(&self) -> Result<(), Error> {
if self.writable {
Ok(())
} else {
Err(Error::MailboxReadOnly)
}
}
pub(super) fn uid_index(&self, uid: Uid) -> Option<usize> {
self.messages.binary_search_by_key(&uid, |m| m.uid).ok()
}
pub(super) fn seqnum_range_to_indices(
&self,
seqnums: &SeqRange<Seqnum>,
silent: bool,
) -> Result<SeqRange<u32>, Error> {
let mut ret = SeqRange::new();
for seqnum in seqnums.items(if silent {
Seqnum::from_index(self.messages.len().saturating_sub(1)).into()
} else {
u32::MAX
}) {
let index = seqnum.to_index();
if index < self.messages.len() {
ret.append(index as u32);
} else if !silent {
return Err(Error::NxMessage);
}
}
Ok(ret)
}
pub(super) fn seqnum_range_to_uid(
&self,
seqnums: &SeqRange<Seqnum>,
silent: bool,
) -> Result<SeqRange<Uid>, Error> {
let mut ret = SeqRange::new();
for seqnum in seqnums.items(if silent {
Seqnum::from_index(self.messages.len().saturating_sub(1)).into()
} else {
u32::MAX
}) {
let index = seqnum.to_index();
if let Some(message) = self.messages.get(index) {
ret.append(message.uid);
} else if !silent {
return Err(Error::NxMessage);
}
}
Ok(ret)
}
pub(super) fn filter_uid_range<'a>(
&self,
uids: &'a SeqRange<Uid>,
) -> Cow<'a, SeqRange<Uid>> {
if uids.len() > self.messages.len() {
let mut ret = SeqRange::new();
for m in self.messages.iter() {
if uids.contains(m.uid) {
ret.append(m.uid);
}
}
Cow::Owned(ret)
} else if uids
.items(u32::MAX)
.all(|uid| self.uid_index(uid).is_some())
{
Cow::Borrowed(uids)
} else {
let mut ret = SeqRange::new();
for uid in uids.items(u32::MAX) {
if self.uid_index(uid).is_some() {
ret.append(uid);
}
}
Cow::Owned(ret)
}
}
pub fn uid_range_to_seqnum(
&self,
uids: &SeqRange<Uid>,
silent: bool,
) -> Result<SeqRange<Seqnum>, Error> {
let mut ret = SeqRange::new();
if uids.len() >= self.messages.len() && silent {
for (ix, m) in self.messages.iter().enumerate() {
if uids.contains(m.uid) {
ret.append(Seqnum::from_index(ix));
}
}
} else {
for uid in uids.items(u32::MAX) {
if let Some(index) = self.uid_index(uid) {
ret.append(Seqnum::from_index(index));
} else if !silent {
return Err(Error::NxMessage);
}
}
}
Ok(ret)
}
pub(super) fn uid_range_to_indices(
&self,
uids: &SeqRange<Uid>,
silent: bool,
) -> Result<SeqRange<u32>, Error> {
let mut ret = SeqRange::new();
if uids.len() >= self.messages.len() && silent {
for (ix, m) in self.messages.iter().enumerate() {
if uids.contains(m.uid) {
ret.append(ix as u32);
}
}
} else {
for uid in uids.items(u32::MAX) {
if let Some(index) = self.uid_index(uid) {
ret.append(index as u32);
} else if !silent {
return Err(Error::NxMessage);
}
}
}
Ok(ret)
}
pub fn flag_id(&self, flag: &Flag) -> Option<storage::FlagId> {
self.flags
.iter()
.find(|&&(_, ref f)| flag == f)
.map(|&(id, _)| id)
}
pub fn count_unseen(&self) -> usize {
let Some(unseen) = self.flag_id(&Flag::Seen) else {
return 0;
};
self.messages
.iter()
.filter(|m| !m.flags.contains(unseen.0))
.count()
}
pub fn count_deleted(&self) -> usize {
let Some(deleted) = self.flag_id(&Flag::Deleted) else {
return 0;
};
self.messages
.iter()
.filter(|m| m.flags.contains(deleted.0))
.count()
}
#[cfg(test)]
pub fn test_flag_o(&self, flag: &Flag, message: Uid) -> bool {
let Some(flag_id) = self.flag_id(flag) else {
return false;
};
let Some(index) = self.uid_index(message) else {
return false;
};
self.messages[index].flags.contains(flag_id.0)
}
pub(super) fn flags_response_if_changed(&mut self) -> Vec<Flag> {
let greatest_id = self
.flags
.last()
.expect("there is always at least one flag")
.0;
if greatest_id > self.max_client_known_flag_id {
self.max_client_known_flag_id = greatest_id;
self.flags.iter().map(|&(_, ref f)| f.clone()).collect()
} else {
vec![]
}
}
}
#[cfg(test)]
pub(super) struct TestFixture {
pub(super) root: tempfile::TempDir,
pub(super) account: Account,
}
#[cfg(test)]
impl TestFixture {
pub(super) fn new() -> Self {
let root = tempfile::TempDir::new().unwrap();
let mut account = Account::new(
LogPrefix::new("account".to_owned()),
root.path().to_owned(),
std::sync::Arc::new(crate::crypt::master_key::MasterKey::new()),
)
.unwrap();
account.key_store.set_rsa_bits(1024);
account.provision(b"hunter2").unwrap();
Self { root, account }
}
pub(super) fn create(&mut self, name: &str) {
self.account
.create(CreateRequest {
name: name.to_owned(),
special_use: vec![],
})
.unwrap();
}
pub(super) fn simple_append(&mut self, dst: &str) -> Uid {
self.simple_append_data(dst, "foobar".as_bytes())
}
pub(super) fn simple_append_data(&mut self, dst: &str, data: &[u8]) -> Uid {
use crate::support::chronox::*;
self.account
.append(
dst,
FixedOffset::zero()
.from_utc_datetime(&Utc::now().naive_local()),
std::iter::empty(),
data,
)
.unwrap()
}
}
#[cfg(test)]
impl std::ops::Deref for TestFixture {
type Target = Account;
fn deref(&self) -> &Account {
&self.account
}
}
#[cfg(test)]
impl std::ops::DerefMut for TestFixture {
fn deref_mut(&mut self) -> &mut Account {
&mut self.account
}
}