Support read-only calendar store as preparation for birthday calendars

This commit is contained in:
Lennart
2025-01-06 17:28:40 +01:00
parent 357b115c62
commit 6a7e839f35
6 changed files with 76 additions and 29 deletions

View File

@@ -89,7 +89,16 @@ pub enum CalendarProp {
} }
#[derive(Clone, Debug, From, Into)] #[derive(Clone, Debug, From, Into)]
pub struct CalendarResource(Calendar); pub struct CalendarResource {
pub cal: Calendar,
pub read_only: bool,
}
impl From<CalendarResource> for Calendar {
fn from(value: CalendarResource) -> Self {
value.cal
}
}
impl Resource for CalendarResource { impl Resource for CalendarResource {
type PropName = CalendarPropName; type PropName = CalendarPropName;
@@ -98,7 +107,7 @@ impl Resource for CalendarResource {
type PrincipalResource = PrincipalResource; type PrincipalResource = PrincipalResource;
fn get_resourcetype(&self) -> Resourcetype { fn get_resourcetype(&self) -> Resourcetype {
if self.0.subscription_url.is_none() { if self.cal.subscription_url.is_none() {
Resourcetype(&[ Resourcetype(&[
ResourcetypeInner(rustical_dav::namespace::NS_DAV, "collection"), ResourcetypeInner(rustical_dav::namespace::NS_DAV, "collection"),
ResourcetypeInner(rustical_dav::namespace::NS_CALDAV, "calendar"), ResourcetypeInner(rustical_dav::namespace::NS_CALDAV, "calendar"),
@@ -118,18 +127,20 @@ impl Resource for CalendarResource {
prop: &Self::PropName, prop: &Self::PropName,
) -> Result<Self::Prop, Self::Error> { ) -> Result<Self::Prop, Self::Error> {
Ok(match prop { Ok(match prop {
CalendarPropName::Displayname => CalendarProp::Displayname(self.0.displayname.clone()), CalendarPropName::Displayname => {
CalendarPropName::CalendarColor => CalendarProp::CalendarColor(self.0.color.clone()), CalendarProp::Displayname(self.cal.displayname.clone())
}
CalendarPropName::CalendarColor => CalendarProp::CalendarColor(self.cal.color.clone()),
CalendarPropName::CalendarDescription => { CalendarPropName::CalendarDescription => {
CalendarProp::CalendarDescription(self.0.description.clone()) CalendarProp::CalendarDescription(self.cal.description.clone())
} }
CalendarPropName::CalendarTimezone => { CalendarPropName::CalendarTimezone => {
CalendarProp::CalendarTimezone(self.0.timezone.clone()) CalendarProp::CalendarTimezone(self.cal.timezone.clone())
} }
CalendarPropName::CalendarTimezoneId => { CalendarPropName::CalendarTimezoneId => {
CalendarProp::CalendarTimezoneId(self.0.timezone_id.clone()) CalendarProp::CalendarTimezoneId(self.cal.timezone_id.clone())
} }
CalendarPropName::CalendarOrder => CalendarProp::CalendarOrder(Some(self.0.order)), CalendarPropName::CalendarOrder => CalendarProp::CalendarOrder(Some(self.cal.order)),
CalendarPropName::SupportedCalendarComponentSet => { CalendarPropName::SupportedCalendarComponentSet => {
CalendarProp::SupportedCalendarComponentSet(SupportedCalendarComponentSet::default()) CalendarProp::SupportedCalendarComponentSet(SupportedCalendarComponentSet::default())
} }
@@ -142,7 +153,8 @@ impl Resource for CalendarResource {
CalendarPropName::Transports => CalendarProp::Transports(Default::default()), CalendarPropName::Transports => CalendarProp::Transports(Default::default()),
CalendarPropName::Topic => { CalendarPropName::Topic => {
// TODO: Add salt since this could be public // TODO: Add salt since this could be public
let url = CalendarResource::get_url(rmap, [&self.0.principal, &self.0.id]).unwrap(); let url =
CalendarResource::get_url(rmap, [&self.cal.principal, &self.cal.id]).unwrap();
let mut hasher = Sha256::new(); let mut hasher = Sha256::new();
hasher.update(url); hasher.update(url);
let topic = format!("{:x}", hasher.finalize()); let topic = format!("{:x}", hasher.finalize());
@@ -152,39 +164,42 @@ impl Resource for CalendarResource {
CalendarPropName::SupportedReportSet => { CalendarPropName::SupportedReportSet => {
CalendarProp::SupportedReportSet(SupportedReportSet::default()) CalendarProp::SupportedReportSet(SupportedReportSet::default())
} }
CalendarPropName::SyncToken => CalendarProp::SyncToken(self.0.format_synctoken()), CalendarPropName::SyncToken => CalendarProp::SyncToken(self.cal.format_synctoken()),
CalendarPropName::Getctag => CalendarProp::Getctag(self.0.format_synctoken()), CalendarPropName::Getctag => CalendarProp::Getctag(self.cal.format_synctoken()),
CalendarPropName::Source => { CalendarPropName::Source => {
CalendarProp::Source(self.0.subscription_url.to_owned().map(HrefElement::from)) CalendarProp::Source(self.cal.subscription_url.to_owned().map(HrefElement::from))
} }
}) })
} }
fn set_prop(&mut self, prop: Self::Prop) -> Result<(), rustical_dav::Error> { fn set_prop(&mut self, prop: Self::Prop) -> Result<(), rustical_dav::Error> {
if self.read_only {
return Err(rustical_dav::Error::PropReadOnly);
}
match prop { match prop {
CalendarProp::Displayname(displayname) => { CalendarProp::Displayname(displayname) => {
self.0.displayname = displayname; self.cal.displayname = displayname;
Ok(()) Ok(())
} }
CalendarProp::CalendarColor(color) => { CalendarProp::CalendarColor(color) => {
self.0.color = color; self.cal.color = color;
Ok(()) Ok(())
} }
CalendarProp::CalendarDescription(description) => { CalendarProp::CalendarDescription(description) => {
self.0.description = description; self.cal.description = description;
Ok(()) Ok(())
} }
CalendarProp::CalendarTimezone(timezone) => { CalendarProp::CalendarTimezone(timezone) => {
self.0.timezone = timezone; self.cal.timezone = timezone;
Ok(()) Ok(())
} }
CalendarProp::CalendarTimezoneId(timezone_id) => { CalendarProp::CalendarTimezoneId(timezone_id) => {
// TODO: Set or remove timezone accordingly // TODO: Set or remove timezone accordingly
self.0.timezone_id = timezone_id; self.cal.timezone_id = timezone_id;
Ok(()) Ok(())
} }
CalendarProp::CalendarOrder(order) => { CalendarProp::CalendarOrder(order) => {
self.0.order = order.unwrap_or_default(); self.cal.order = order.unwrap_or_default();
Ok(()) Ok(())
} }
CalendarProp::SupportedCalendarComponentSet(_) => { CalendarProp::SupportedCalendarComponentSet(_) => {
@@ -204,29 +219,32 @@ impl Resource for CalendarResource {
} }
fn remove_prop(&mut self, prop: &Self::PropName) -> Result<(), rustical_dav::Error> { fn remove_prop(&mut self, prop: &Self::PropName) -> Result<(), rustical_dav::Error> {
if self.read_only {
return Err(rustical_dav::Error::PropReadOnly);
}
match prop { match prop {
CalendarPropName::Displayname => { CalendarPropName::Displayname => {
self.0.displayname = None; self.cal.displayname = None;
Ok(()) Ok(())
} }
CalendarPropName::CalendarColor => { CalendarPropName::CalendarColor => {
self.0.color = None; self.cal.color = None;
Ok(()) Ok(())
} }
CalendarPropName::CalendarDescription => { CalendarPropName::CalendarDescription => {
self.0.description = None; self.cal.description = None;
Ok(()) Ok(())
} }
CalendarPropName::CalendarTimezone => { CalendarPropName::CalendarTimezone => {
self.0.timezone = None; self.cal.timezone = None;
Ok(()) Ok(())
} }
CalendarPropName::CalendarTimezoneId => { CalendarPropName::CalendarTimezoneId => {
self.0.timezone_id = None; self.cal.timezone_id = None;
Ok(()) Ok(())
} }
CalendarPropName::CalendarOrder => { CalendarPropName::CalendarOrder => {
self.0.order = 0; self.cal.order = 0;
Ok(()) Ok(())
} }
CalendarPropName::SupportedCalendarComponentSet => { CalendarPropName::SupportedCalendarComponentSet => {
@@ -251,12 +269,15 @@ impl Resource for CalendarResource {
} }
fn get_owner(&self) -> Option<&str> { fn get_owner(&self) -> Option<&str> {
Some(&self.0.principal) Some(&self.cal.principal)
} }
fn get_user_privileges(&self, user: &User) -> Result<UserPrivilegeSet, Self::Error> { fn get_user_privileges(&self, user: &User) -> Result<UserPrivilegeSet, Self::Error> {
// TODO: read-only for subscription if self.cal.subscription_url.is_some() {
Ok(UserPrivilegeSet::owner_only(self.0.principal == user.id)) return Ok(UserPrivilegeSet::read_only());
}
Ok(UserPrivilegeSet::owner_only(self.cal.principal == user.id))
} }
} }
@@ -276,7 +297,10 @@ impl<C: CalendarStore + ?Sized> ResourceService for CalendarResourceService<C> {
.get_calendar(principal, cal_id) .get_calendar(principal, cal_id)
.await .await
.map_err(|_e| Error::NotFound)?; .map_err(|_e| Error::NotFound)?;
Ok(calendar.into()) Ok(CalendarResource {
cal: calendar,
read_only: self.cal_store.is_read_only(),
})
} }
async fn get_members( async fn get_members(

View File

@@ -122,7 +122,10 @@ impl<C: CalendarStore + ?Sized> ResourceService for PrincipalResourceService<C>
.map(|cal| { .map(|cal| {
( (
CalendarResource::get_url(rmap, vec![principal, &cal.id]).unwrap(), CalendarResource::get_url(rmap, vec![principal, &cal.id]).unwrap(),
cal.into(), CalendarResource {
cal,
read_only: self.cal_store.is_read_only(),
},
) )
}) })
.collect()) .collect())

View File

@@ -63,6 +63,16 @@ impl UserPrivilegeSet {
Self::default() Self::default()
} }
} }
pub fn read_only() -> Self {
Self {
privileges: HashSet::from([
UserPrivilege::Read,
UserPrivilege::ReadAcl,
UserPrivilege::ReadCurrentUserPrivilegeSet,
]),
}
}
} }
impl<const N: usize> From<[UserPrivilege; N]> for UserPrivilegeSet { impl<const N: usize> From<[UserPrivilege; N]> for UserPrivilegeSet {

View File

@@ -61,4 +61,6 @@ pub trait CalendarStore: Send + Sync + 'static {
cal_id: &str, cal_id: &str,
object_id: &str, object_id: &str,
) -> Result<(), Error>; ) -> Result<(), Error>;
fn is_read_only(&self) -> bool;
} }

View File

@@ -140,4 +140,8 @@ impl<AS: AddressbookStore> CalendarStore for ContactBirthdayStore<AS> {
) -> Result<(), Error> { ) -> Result<(), Error> {
Err(Error::ReadOnly) Err(Error::ReadOnly)
} }
fn is_read_only(&self) -> bool {
true
}
} }

View File

@@ -379,4 +379,8 @@ impl CalendarStore for SqliteStore {
Ok((objects, deleted_objects, new_synctoken)) Ok((objects, deleted_objects, new_synctoken))
} }
fn is_read_only(&self) -> bool {
false
}
} }