Calendar Namespace
This commit is contained in:
parent
ed47855ef1
commit
bc0f897803
4 changed files with 310 additions and 15 deletions
|
@ -1,5 +1,20 @@
|
|||
pub mod namespace;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use aero_user::login::Credentials;
|
||||
|
||||
use crate::unique_ident::*;
|
||||
|
||||
pub struct Calendar {
|
||||
a: u64,
|
||||
}
|
||||
|
||||
impl Calendar {
|
||||
pub(crate) async fn open(
|
||||
creds: &Credentials,
|
||||
id: UniqueIdent,
|
||||
) -> Result<Self> {
|
||||
todo!();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,47 +1,327 @@
|
|||
use anyhow::Result;
|
||||
use anyhow::{bail, Result};
|
||||
use std::collections::{HashMap, BTreeMap};
|
||||
use std::sync::{Weak, Arc};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use aero_bayou::timestamp::now_msec;
|
||||
use aero_user::storage;
|
||||
use aero_user::cryptoblob::{open_deserialize, seal_serialize};
|
||||
|
||||
use crate::unique_ident::UniqueIdent;
|
||||
use crate::unique_ident::{gen_ident, UniqueIdent};
|
||||
use crate::user::User;
|
||||
use super::Calendar;
|
||||
|
||||
pub(crate) const CAL_LIST_PK: &str = "calendars";
|
||||
pub(crate) const CAL_LIST_SK: &str = "list";
|
||||
pub(crate) const MAIN_CAL: &str = "Personal";
|
||||
pub(crate) const MAX_CALNAME_CHARS: usize = 32;
|
||||
|
||||
pub(crate) struct CalendarNs(std::sync::Mutex<HashMap<UniqueIdent, Weak<Calendar>>>);
|
||||
|
||||
impl CalendarNs {
|
||||
/// Create a new calendar namespace
|
||||
pub fn new() -> Self {
|
||||
Self(std::sync::Mutex::new(HashMap::new()))
|
||||
}
|
||||
|
||||
pub fn list(&self) {
|
||||
todo!();
|
||||
/// Open a calendar by name
|
||||
pub async fn open(&self, user: &Arc<User>, name: &str) -> Result<Option<Arc<Calendar>>> {
|
||||
let (list, _ct) = CalendarList::load(user).await?;
|
||||
|
||||
match list.get(name) {
|
||||
None => Ok(None),
|
||||
Some(ident) => Ok(Some(self.open_by_id(user, ident).await?)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Open a calendar by unique id
|
||||
/// Check user.rs::open_mailbox_by_id to understand this function
|
||||
pub async fn open_by_id(&self, user: &Arc<User>, id: UniqueIdent) -> Result<Arc<Calendar>> {
|
||||
{
|
||||
let cache = self.0.lock().unwrap();
|
||||
if let Some(cal) = cache.get(&id).and_then(Weak::upgrade) {
|
||||
return Ok(cal);
|
||||
}
|
||||
}
|
||||
|
||||
let cal = Arc::new(Calendar::open(&user.creds, id).await?);
|
||||
|
||||
let mut cache = self.0.lock().unwrap();
|
||||
if let Some(concurrent_cal) = cache.get(&id).and_then(Weak::upgrade) {
|
||||
drop(cal); // we worked for nothing but at least we didn't starve someone else
|
||||
Ok(concurrent_cal)
|
||||
} else {
|
||||
cache.insert(id, Arc::downgrade(&cal));
|
||||
Ok(cal)
|
||||
}
|
||||
}
|
||||
|
||||
/// List calendars
|
||||
pub async fn list(&self, user: &Arc<User>) -> Result<Vec<String>> {
|
||||
CalendarList::load(user).await.map(|(list, _)| list.names())
|
||||
}
|
||||
|
||||
/// Delete a calendar from the index
|
||||
pub async fn delete(&self, user: &Arc<User>, name: &str) -> Result<()> {
|
||||
// We currently assume that main cal is a bit specific
|
||||
if name == MAIN_CAL {
|
||||
bail!("Cannot delete main calendar");
|
||||
}
|
||||
|
||||
let (mut list, ct) = CalendarList::load(user).await?;
|
||||
if list.has(name) {
|
||||
//@TODO: actually delete calendar content
|
||||
list.bind(name, None);
|
||||
list.save(user, ct).await?;
|
||||
Ok(())
|
||||
} else {
|
||||
bail!("Calendar {} does not exist", name);
|
||||
}
|
||||
}
|
||||
|
||||
/// Rename a calendar in the index
|
||||
pub async fn rename(&self, user: &Arc<User>, old: &str, new: &str) -> Result<()> {
|
||||
if old == MAIN_CAL {
|
||||
bail!("Renaming main calendar is not supported currently");
|
||||
}
|
||||
if !new.chars().all(char::is_alphanumeric) {
|
||||
bail!("Unsupported characters in new calendar name, only alphanumeric characters are allowed currently");
|
||||
}
|
||||
if new.len() > MAX_CALNAME_CHARS {
|
||||
bail!("Calendar name can't contain more than 32 characters");
|
||||
}
|
||||
|
||||
let (mut list, ct) = CalendarList::load(user).await?;
|
||||
list.rename(old, new)?;
|
||||
list.save(user, ct).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create calendar
|
||||
pub async fn create(&self, user: &Arc<User>, name: &str) -> Result<()> {
|
||||
if name == MAIN_CAL {
|
||||
bail!("Main calendar is automatically created, can't create it manually");
|
||||
}
|
||||
if !name.chars().all(char::is_alphanumeric) {
|
||||
bail!("Unsupported characters in new calendar name, only alphanumeric characters are allowed");
|
||||
}
|
||||
if name.len() > MAX_CALNAME_CHARS {
|
||||
bail!("Calendar name can't contain more than 32 characters");
|
||||
}
|
||||
|
||||
let (mut list, ct) = CalendarList::load(user).await?;
|
||||
match list.create(name) {
|
||||
CalendarExists::Existed(_) => bail!("Calendar {} already exists", name),
|
||||
CalendarExists::Created(_) => (),
|
||||
}
|
||||
list.save(user, ct).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Has calendar
|
||||
pub async fn has(&self, user: &Arc<User>, name: &str) -> Result<bool> {
|
||||
CalendarList::load(user).await.map(|(list, _)| list.has(name))
|
||||
}
|
||||
}
|
||||
|
||||
// ------
|
||||
// ------ From this point, implementation is hidden from the rest of the crate
|
||||
// ------
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub(crate) struct CalendarList(BTreeMap<String, CalendarListEntry>);
|
||||
struct CalendarList(BTreeMap<String, CalendarListEntry>);
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Copy, Debug)]
|
||||
pub(crate) struct CalendarListEntry {
|
||||
struct CalendarListEntry {
|
||||
id_lww: (u64, Option<UniqueIdent>),
|
||||
}
|
||||
|
||||
impl CalendarList {
|
||||
pub(crate) async fn load(user: &Arc<User>) -> Result<(Self, Option<storage::RowRef>)> {
|
||||
todo!();
|
||||
// ---- Index persistence related functions
|
||||
|
||||
/// Load from storage
|
||||
async fn load(user: &Arc<User>) -> Result<(Self, Option<storage::RowRef>)> {
|
||||
let row_ref = storage::RowRef::new(CAL_LIST_PK, CAL_LIST_SK);
|
||||
let (mut list, row) = match user
|
||||
.storage
|
||||
.row_fetch(&storage::Selector::Single(&row_ref))
|
||||
.await
|
||||
{
|
||||
Err(storage::StorageError::NotFound) => (Self::new(), None),
|
||||
Err(e) => return Err(e.into()),
|
||||
Ok(rv) => {
|
||||
let mut list = Self::new();
|
||||
let (row_ref, row_vals) = match rv.into_iter().next() {
|
||||
Some(row_val) => (row_val.row_ref, row_val.value),
|
||||
None => (row_ref, vec![]),
|
||||
};
|
||||
|
||||
for v in row_vals {
|
||||
if let storage::Alternative::Value(vbytes) = v {
|
||||
let list2 = open_deserialize::<CalendarList>(&vbytes, &user.creds.keys.master)?;
|
||||
list.merge(list2);
|
||||
}
|
||||
}
|
||||
(list, Some(row_ref))
|
||||
}
|
||||
};
|
||||
|
||||
// Create default calendars (currently only one calendar is created)
|
||||
let is_default_cal_missing = [MAIN_CAL]
|
||||
.iter()
|
||||
.map(|calname| list.create(calname))
|
||||
.fold(false, |acc, r| {
|
||||
acc || matches!(r, CalendarExists::Created(..))
|
||||
});
|
||||
|
||||
// Save the index if we created a new calendar
|
||||
if is_default_cal_missing {
|
||||
list.save(user, row.clone()).await?;
|
||||
}
|
||||
|
||||
Ok((list, row))
|
||||
}
|
||||
|
||||
pub(crate) async fn save(user: &Arc<User>, ct: Option<storage::RowRef>) -> Result<()> {
|
||||
todo!();
|
||||
/// Save an updated index
|
||||
async fn save(&self, user: &Arc<User>, ct: Option<storage::RowRef>) -> Result<()> {
|
||||
let list_blob = seal_serialize(self, &user.creds.keys.master)?;
|
||||
let rref = ct.unwrap_or(storage::RowRef::new(CAL_LIST_PK, CAL_LIST_SK));
|
||||
let row_val = storage::RowVal::new(rref, list_blob);
|
||||
user.storage.row_insert(vec![row_val]).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn new() -> Self {
|
||||
// ----- Index manipulation functions
|
||||
|
||||
/// Ensure that a given calendar exists
|
||||
/// (Don't forget to save if it returns CalendarExists::Created)
|
||||
fn create(&mut self, name: &str) -> CalendarExists {
|
||||
if let Some(CalendarListEntry {
|
||||
id_lww: (_, Some(id))
|
||||
}) = self.0.get(name)
|
||||
{
|
||||
return CalendarExists::Existed(*id);
|
||||
}
|
||||
|
||||
let id = gen_ident();
|
||||
self.bind(name, Some(id)).unwrap();
|
||||
CalendarExists::Created(id)
|
||||
}
|
||||
|
||||
/// Get a list of all calendar names
|
||||
fn names(&self) -> Vec<String> {
|
||||
self.0
|
||||
.iter()
|
||||
.filter(|(_, v)| v.id_lww.1.is_some())
|
||||
.map(|(k, _)| k.to_string())
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// For a given calendar name, get its Unique Identifier
|
||||
fn get(&self, name: &str) -> Option<UniqueIdent> {
|
||||
self.0.get(name).map(|CalendarListEntry {
|
||||
id_lww: (_, ident),
|
||||
}| *ident).flatten()
|
||||
}
|
||||
|
||||
/// Check if a given calendar name exists
|
||||
fn has(&self, name: &str) -> bool {
|
||||
self.get(name).is_some()
|
||||
}
|
||||
|
||||
/// Rename a calendar
|
||||
fn rename(&mut self, old: &str, new: &str) -> Result<()> {
|
||||
if self.has(new) {
|
||||
bail!("Calendar {} already exists", new);
|
||||
}
|
||||
let ident = match self.get(old) {
|
||||
None => bail!("Calendar {} does not exist", old),
|
||||
Some(ident) => ident,
|
||||
};
|
||||
|
||||
self.bind(old, None);
|
||||
self.bind(new, Some(ident));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ----- Internal logic
|
||||
|
||||
/// New is not publicly exposed, use `load` instead
|
||||
fn new() -> Self {
|
||||
Self(BTreeMap::new())
|
||||
}
|
||||
|
||||
/// Low level index updating logic (used to add/rename/delete) an entry
|
||||
fn bind(&mut self, name: &str, id: Option<UniqueIdent>) -> Option<()> {
|
||||
let (ts, id) = match self.0.get_mut(name) {
|
||||
None => {
|
||||
if id.is_none() {
|
||||
// User wants to delete entry with given name (passed id is None)
|
||||
// Entry does not exist (get_mut is None)
|
||||
// Nothing to do
|
||||
return None;
|
||||
} else {
|
||||
// User wants entry with given name to be present (id is Some)
|
||||
// Entry does not exist
|
||||
// Initialize entry
|
||||
(now_msec(), id)
|
||||
}
|
||||
}
|
||||
Some(CalendarListEntry {
|
||||
id_lww,
|
||||
}) => {
|
||||
if id_lww.1 == id {
|
||||
// Entry is already equals to the requested id (Option<UniqueIdent)
|
||||
// Nothing to do
|
||||
return None;
|
||||
} else {
|
||||
// Entry does not equal to what we know internally
|
||||
// We update the Last Write Win CRDT here with the new id value
|
||||
(
|
||||
std::cmp::max(id_lww.0 + 1, now_msec()),
|
||||
id,
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// If we did not return here, that's because we have to update
|
||||
// something in our internal index.
|
||||
self.0.insert(
|
||||
name.into(),
|
||||
CalendarListEntry { id_lww: (ts, id) },
|
||||
);
|
||||
Some(())
|
||||
}
|
||||
|
||||
// Merge 2 calendar lists by applying a LWW logic on each element
|
||||
fn merge(&mut self, list2: Self) {
|
||||
for (k, v) in list2.0.into_iter() {
|
||||
if let Some(e) = self.0.get_mut(&k) {
|
||||
e.merge(&v);
|
||||
} else {
|
||||
self.0.insert(k, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CalendarListEntry {
|
||||
fn merge(&mut self, other: &Self) {
|
||||
// Simple CRDT merge rule
|
||||
if other.id_lww.0 > self.id_lww.0
|
||||
|| (other.id_lww.0 == self.id_lww.0 && other.id_lww.1 > self.id_lww.1)
|
||||
{
|
||||
self.id_lww = other.id_lww;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum CalendarExists {
|
||||
Created(UniqueIdent),
|
||||
Existed(UniqueIdent),
|
||||
}
|
||||
|
|
|
@ -8,8 +8,8 @@ use aero_user::storage::{self, BlobRef, BlobVal, RowRef, RowVal, Selector, Store
|
|||
use aero_bayou::Bayou;
|
||||
use aero_bayou::timestamp::now_msec;
|
||||
|
||||
use crate::mail::uidindex::*;
|
||||
use crate::unique_ident::*;
|
||||
use crate::mail::uidindex::*;
|
||||
use crate::mail::IMF;
|
||||
|
||||
pub struct Mailbox {
|
||||
|
|
|
@ -14,7 +14,7 @@ use crate::mail::mailbox::Mailbox;
|
|||
use crate::mail::uidindex::ImapUidvalidity;
|
||||
use crate::unique_ident::UniqueIdent;
|
||||
use crate::mail::namespace::{MAILBOX_HIERARCHY_DELIMITER, INBOX, DRAFTS, ARCHIVE, SENT, TRASH, MAILBOX_LIST_PK, MAILBOX_LIST_SK,MailboxList,CreatedMailbox};
|
||||
use crate::calendar::Calendar;
|
||||
use crate::calendar::namespace::CalendarNs;
|
||||
|
||||
//@FIXME User should be totally rewriten
|
||||
// to extract the local mailbox list
|
||||
|
@ -29,7 +29,7 @@ pub struct User {
|
|||
pub creds: Credentials,
|
||||
pub storage: storage::Store,
|
||||
pub mailboxes: std::sync::Mutex<HashMap<UniqueIdent, Weak<Mailbox>>>,
|
||||
pub calendars: std::sync::Mutex<HashMap<UniqueIdent, Weak<Calendar>>>,
|
||||
pub calendars: CalendarNs,
|
||||
|
||||
// Handle on worker processing received email
|
||||
// (moving emails from the mailqueue to the user's INBOX)
|
||||
|
@ -186,7 +186,7 @@ impl User {
|
|||
storage,
|
||||
tx_inbox_id,
|
||||
mailboxes: std::sync::Mutex::new(HashMap::new()),
|
||||
calendars: std::sync::Mutex::new(HashMap::new()),
|
||||
calendars: CalendarNs::new(),
|
||||
});
|
||||
|
||||
// Ensure INBOX exists (done inside load_mailbox_list)
|
||||
|
|
Loading…
Reference in a new issue