mirror of
https://github.com/lennart-k/rustical.git
synced 2025-12-14 03:32:15 +00:00
Dav Push: Support for calendar collections
This commit is contained in:
@@ -2,7 +2,11 @@ use actix_web::{
|
||||
web::{self, Data, Path},
|
||||
HttpResponse,
|
||||
};
|
||||
use rustical_dav::xml::multistatus::PropstatElement;
|
||||
use rustical_store::SubscriptionStore;
|
||||
use rustical_xml::{XmlRootTag, XmlSerialize};
|
||||
|
||||
use crate::calendar::resource::CalendarProp;
|
||||
|
||||
async fn handle_delete<S: SubscriptionStore + ?Sized>(
|
||||
store: Data<S>,
|
||||
@@ -18,3 +22,9 @@ pub fn subscription_resource<S: SubscriptionStore + ?Sized>() -> actix_web::Reso
|
||||
.name("subscription")
|
||||
.delete(handle_delete::<S>)
|
||||
}
|
||||
|
||||
#[derive(XmlSerialize, XmlRootTag)]
|
||||
#[xml(root = b"push-message", ns = "rustical_dav::namespace::NS_DAVPUSH")]
|
||||
pub struct PushMessage {
|
||||
propstat: PropstatElement<CalendarProp>,
|
||||
}
|
||||
|
||||
@@ -14,10 +14,12 @@ pub struct PropTagWrapper<T: XmlSerialize>(#[xml(flatten, ty = "untagged")] pub
|
||||
|
||||
// RFC 2518
|
||||
// <!ELEMENT propstat (prop, status, responsedescription?) >
|
||||
#[derive(XmlSerialize)]
|
||||
#[derive(XmlSerialize, Debug)]
|
||||
pub struct PropstatElement<PropType: XmlSerialize> {
|
||||
#[xml(ns = "crate::namespace::NS_DAV")]
|
||||
pub prop: PropType,
|
||||
#[xml(serialize_with = "xml_serialize_status")]
|
||||
#[xml(ns = "crate::namespace::NS_DAV")]
|
||||
pub status: StatusCode,
|
||||
}
|
||||
|
||||
|
||||
@@ -16,3 +16,24 @@ pub use subscription_store::*;
|
||||
|
||||
pub use addressbook::{AddressObject, Addressbook};
|
||||
pub use calendar::{Calendar, CalendarObject};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum CollectionOperationType {
|
||||
// Sync-Token increased
|
||||
Object,
|
||||
Delete,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum CollectionOperationDomain {
|
||||
Calendar,
|
||||
Addressbook,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CollectionOperation {
|
||||
pub r#type: CollectionOperationType,
|
||||
pub domain: CollectionOperationDomain,
|
||||
pub topic: String,
|
||||
pub sync_token: Option<String>,
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ pub struct Subscription {
|
||||
pub push_resource: String,
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
#[async_trait]
|
||||
pub trait SubscriptionStore: Send + Sync + 'static {
|
||||
async fn get_subscriptions(&self, topic: &str) -> Result<Vec<Subscription>, Error>;
|
||||
async fn get_subscription(&self, id: &str) -> Result<Subscription, Error>;
|
||||
|
||||
@@ -7,6 +7,7 @@ repository.workspace = true
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
tokio.workspace = true
|
||||
rustical_store = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
use super::{ChangeOperation, SqliteStore};
|
||||
use super::ChangeOperation;
|
||||
use async_trait::async_trait;
|
||||
use rustical_store::synctoken::format_synctoken;
|
||||
use rustical_store::{Calendar, CalendarObject, CalendarStore, Error};
|
||||
use rustical_store::{CollectionOperation, CollectionOperationType};
|
||||
use sqlx::Sqlite;
|
||||
use sqlx::SqlitePool;
|
||||
use sqlx::Transaction;
|
||||
use tokio::sync::mpsc::Sender;
|
||||
use tracing::instrument;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -26,16 +30,21 @@ async fn log_object_operation(
|
||||
cal_id: &str,
|
||||
object_id: &str,
|
||||
operation: ChangeOperation,
|
||||
) -> Result<(), Error> {
|
||||
sqlx::query!(
|
||||
) -> Result<String, Error> {
|
||||
struct Synctoken {
|
||||
synctoken: i64,
|
||||
}
|
||||
let Synctoken { synctoken } = sqlx::query_as!(
|
||||
Synctoken,
|
||||
r#"
|
||||
UPDATE calendars
|
||||
SET synctoken = synctoken + 1
|
||||
WHERE (principal, id) = (?1, ?2)"#,
|
||||
WHERE (principal, id) = (?1, ?2)
|
||||
RETURNING synctoken"#,
|
||||
principal,
|
||||
cal_id
|
||||
)
|
||||
.execute(&mut **tx)
|
||||
.fetch_one(&mut **tx)
|
||||
.await
|
||||
.map_err(crate::Error::from)?;
|
||||
|
||||
@@ -53,11 +62,23 @@ async fn log_object_operation(
|
||||
.execute(&mut **tx)
|
||||
.await
|
||||
.map_err(crate::Error::from)?;
|
||||
Ok(())
|
||||
Ok(format_synctoken(synctoken))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SqliteCalendarStore {
|
||||
db: SqlitePool,
|
||||
sender: Sender<CollectionOperation>,
|
||||
}
|
||||
|
||||
impl SqliteCalendarStore {
|
||||
pub fn new(db: SqlitePool, sender: Sender<CollectionOperation>) -> Self {
|
||||
Self { db, sender }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl CalendarStore for SqliteStore {
|
||||
impl CalendarStore for SqliteCalendarStore {
|
||||
#[instrument]
|
||||
async fn get_calendar(&self, principal: &str, id: &str) -> Result<Calendar, Error> {
|
||||
let cal = sqlx::query_as!(
|
||||
@@ -157,6 +178,12 @@ impl CalendarStore for SqliteStore {
|
||||
id: &str,
|
||||
use_trashbin: bool,
|
||||
) -> Result<(), Error> {
|
||||
let cal = match self.get_calendar(principal, id).await {
|
||||
Ok(cal) => Some(cal),
|
||||
Err(Error::NotFound) => None,
|
||||
Err(err) => return Err(err),
|
||||
};
|
||||
|
||||
match use_trashbin {
|
||||
true => {
|
||||
sqlx::query!(
|
||||
@@ -177,6 +204,16 @@ impl CalendarStore for SqliteStore {
|
||||
.map_err(crate::Error::from)?;
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(cal) = cal {
|
||||
// TODO: Watch for errors here?
|
||||
let _ = self.sender.try_send(CollectionOperation {
|
||||
r#type: CollectionOperationType::Delete,
|
||||
domain: rustical_store::CollectionOperationDomain::Calendar,
|
||||
topic: cal.push_topic,
|
||||
sync_token: None,
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -267,7 +304,7 @@ impl CalendarStore for SqliteStore {
|
||||
.await
|
||||
.map_err(crate::Error::from)?;
|
||||
|
||||
log_object_operation(
|
||||
let synctoken = log_object_operation(
|
||||
&mut tx,
|
||||
&principal,
|
||||
&cal_id,
|
||||
@@ -276,6 +313,14 @@ impl CalendarStore for SqliteStore {
|
||||
)
|
||||
.await?;
|
||||
|
||||
// TODO: Watch for errors here?
|
||||
let _ = self.sender.try_send(CollectionOperation {
|
||||
r#type: CollectionOperationType::Object,
|
||||
domain: rustical_store::CollectionOperationDomain::Calendar,
|
||||
topic: self.get_calendar(&principal, &cal_id).await?.push_topic,
|
||||
sync_token: Some(synctoken),
|
||||
});
|
||||
|
||||
tx.commit().await.map_err(crate::Error::from)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -312,8 +357,16 @@ impl CalendarStore for SqliteStore {
|
||||
.map_err(crate::Error::from)?;
|
||||
}
|
||||
};
|
||||
log_object_operation(&mut tx, principal, cal_id, id, ChangeOperation::Delete).await?;
|
||||
let synctoken =
|
||||
log_object_operation(&mut tx, principal, cal_id, id, ChangeOperation::Delete).await?;
|
||||
tx.commit().await.map_err(crate::Error::from)?;
|
||||
// TODO: Watch for errors here?
|
||||
let _ = self.sender.try_send(CollectionOperation {
|
||||
r#type: CollectionOperationType::Object,
|
||||
domain: rustical_store::CollectionOperationDomain::Calendar,
|
||||
topic: self.get_calendar(principal, cal_id).await?.push_topic,
|
||||
sync_token: Some(synctoken),
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -335,8 +388,17 @@ impl CalendarStore for SqliteStore {
|
||||
.execute(&mut *tx)
|
||||
.await.map_err(crate::Error::from)?;
|
||||
|
||||
log_object_operation(&mut tx, principal, cal_id, object_id, ChangeOperation::Add).await?;
|
||||
let synctoken =
|
||||
log_object_operation(&mut tx, principal, cal_id, object_id, ChangeOperation::Add)
|
||||
.await?;
|
||||
tx.commit().await.map_err(crate::Error::from)?;
|
||||
// TODO: Watch for errors here?
|
||||
let _ = self.sender.try_send(CollectionOperation {
|
||||
r#type: CollectionOperationType::Object,
|
||||
domain: rustical_store::CollectionOperationDomain::Calendar,
|
||||
topic: self.get_calendar(principal, cal_id).await?.push_topic,
|
||||
sync_token: Some(synctoken),
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::SqliteStore;
|
||||
use async_trait::async_trait;
|
||||
use rustical_store::{Error, Subscription, SubscriptionStore};
|
||||
|
||||
#[async_trait(?Send)]
|
||||
#[async_trait]
|
||||
impl SubscriptionStore for SqliteStore {
|
||||
async fn get_subscriptions(&self, topic: &str) -> Result<Vec<Subscription>, Error> {
|
||||
Ok(sqlx::query_as!(
|
||||
|
||||
Reference in New Issue
Block a user