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)]
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 {
type PropName = CalendarPropName;
@@ -98,7 +107,7 @@ impl Resource for CalendarResource {
type PrincipalResource = PrincipalResource;
fn get_resourcetype(&self) -> Resourcetype {
if self.0.subscription_url.is_none() {
if self.cal.subscription_url.is_none() {
Resourcetype(&[
ResourcetypeInner(rustical_dav::namespace::NS_DAV, "collection"),
ResourcetypeInner(rustical_dav::namespace::NS_CALDAV, "calendar"),
@@ -118,18 +127,20 @@ impl Resource for CalendarResource {
prop: &Self::PropName,
) -> Result<Self::Prop, Self::Error> {
Ok(match prop {
CalendarPropName::Displayname => CalendarProp::Displayname(self.0.displayname.clone()),
CalendarPropName::CalendarColor => CalendarProp::CalendarColor(self.0.color.clone()),
CalendarPropName::Displayname => {
CalendarProp::Displayname(self.cal.displayname.clone())
}
CalendarPropName::CalendarColor => CalendarProp::CalendarColor(self.cal.color.clone()),
CalendarPropName::CalendarDescription => {
CalendarProp::CalendarDescription(self.0.description.clone())
CalendarProp::CalendarDescription(self.cal.description.clone())
}
CalendarPropName::CalendarTimezone => {
CalendarProp::CalendarTimezone(self.0.timezone.clone())
CalendarProp::CalendarTimezone(self.cal.timezone.clone())
}
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 => {
CalendarProp::SupportedCalendarComponentSet(SupportedCalendarComponentSet::default())
}
@@ -142,7 +153,8 @@ impl Resource for CalendarResource {
CalendarPropName::Transports => CalendarProp::Transports(Default::default()),
CalendarPropName::Topic => {
// 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();
hasher.update(url);
let topic = format!("{:x}", hasher.finalize());
@@ -152,39 +164,42 @@ impl Resource for CalendarResource {
CalendarPropName::SupportedReportSet => {
CalendarProp::SupportedReportSet(SupportedReportSet::default())
}
CalendarPropName::SyncToken => CalendarProp::SyncToken(self.0.format_synctoken()),
CalendarPropName::Getctag => CalendarProp::Getctag(self.0.format_synctoken()),
CalendarPropName::SyncToken => CalendarProp::SyncToken(self.cal.format_synctoken()),
CalendarPropName::Getctag => CalendarProp::Getctag(self.cal.format_synctoken()),
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> {
if self.read_only {
return Err(rustical_dav::Error::PropReadOnly);
}
match prop {
CalendarProp::Displayname(displayname) => {
self.0.displayname = displayname;
self.cal.displayname = displayname;
Ok(())
}
CalendarProp::CalendarColor(color) => {
self.0.color = color;
self.cal.color = color;
Ok(())
}
CalendarProp::CalendarDescription(description) => {
self.0.description = description;
self.cal.description = description;
Ok(())
}
CalendarProp::CalendarTimezone(timezone) => {
self.0.timezone = timezone;
self.cal.timezone = timezone;
Ok(())
}
CalendarProp::CalendarTimezoneId(timezone_id) => {
// TODO: Set or remove timezone accordingly
self.0.timezone_id = timezone_id;
self.cal.timezone_id = timezone_id;
Ok(())
}
CalendarProp::CalendarOrder(order) => {
self.0.order = order.unwrap_or_default();
self.cal.order = order.unwrap_or_default();
Ok(())
}
CalendarProp::SupportedCalendarComponentSet(_) => {
@@ -204,29 +219,32 @@ impl Resource for CalendarResource {
}
fn remove_prop(&mut self, prop: &Self::PropName) -> Result<(), rustical_dav::Error> {
if self.read_only {
return Err(rustical_dav::Error::PropReadOnly);
}
match prop {
CalendarPropName::Displayname => {
self.0.displayname = None;
self.cal.displayname = None;
Ok(())
}
CalendarPropName::CalendarColor => {
self.0.color = None;
self.cal.color = None;
Ok(())
}
CalendarPropName::CalendarDescription => {
self.0.description = None;
self.cal.description = None;
Ok(())
}
CalendarPropName::CalendarTimezone => {
self.0.timezone = None;
self.cal.timezone = None;
Ok(())
}
CalendarPropName::CalendarTimezoneId => {
self.0.timezone_id = None;
self.cal.timezone_id = None;
Ok(())
}
CalendarPropName::CalendarOrder => {
self.0.order = 0;
self.cal.order = 0;
Ok(())
}
CalendarPropName::SupportedCalendarComponentSet => {
@@ -251,12 +269,15 @@ impl Resource for CalendarResource {
}
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> {
// TODO: read-only for subscription
Ok(UserPrivilegeSet::owner_only(self.0.principal == user.id))
if self.cal.subscription_url.is_some() {
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)
.await
.map_err(|_e| Error::NotFound)?;
Ok(calendar.into())
Ok(CalendarResource {
cal: calendar,
read_only: self.cal_store.is_read_only(),
})
}
async fn get_members(

View File

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

View File

@@ -63,6 +63,16 @@ impl UserPrivilegeSet {
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 {

View File

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

View File

@@ -140,4 +140,8 @@ impl<AS: AddressbookStore> CalendarStore for ContactBirthdayStore<AS> {
) -> Result<(), Error> {
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))
}
fn is_read_only(&self) -> bool {
false
}
}