Dav Push: Support for calendar collections

This commit is contained in:
Lennart
2025-01-12 22:31:39 +01:00
parent 974acdf2cb
commit 347061ff8f
10 changed files with 564 additions and 22 deletions

364
Cargo.lock generated
View File

@@ -818,6 +818,16 @@ dependencies = [
"version_check",
]
[[package]]
name = "core-foundation"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
@@ -1105,6 +1115,21 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f"
[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
"foreign-types-shared",
]
[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "form_urlencoded"
version = "1.2.1"
@@ -1442,6 +1467,23 @@ dependencies = [
"want",
]
[[package]]
name = "hyper-rustls"
version = "0.27.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2"
dependencies = [
"futures-util",
"http 1.2.0",
"hyper",
"hyper-util",
"rustls",
"rustls-pki-types",
"tokio",
"tokio-rustls",
"tower-service",
]
[[package]]
name = "hyper-timeout"
version = "0.5.2"
@@ -1455,6 +1497,22 @@ dependencies = [
"tower-service",
]
[[package]]
name = "hyper-tls"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
dependencies = [
"bytes",
"http-body-util",
"hyper",
"hyper-util",
"native-tls",
"tokio",
"tokio-native-tls",
"tower-service",
]
[[package]]
name = "hyper-util"
version = "0.1.10"
@@ -1687,6 +1745,12 @@ dependencies = [
"generic-array",
]
[[package]]
name = "ipnet"
version = "2.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708"
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
@@ -1899,6 +1963,23 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e94e1e6445d314f972ff7395df2de295fe51b71821694f0b0e1e79c4f12c8577"
[[package]]
name = "native-tls"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466"
dependencies = [
"libc",
"log",
"openssl",
"openssl-probe",
"openssl-sys",
"schannel",
"security-framework",
"security-framework-sys",
"tempfile",
]
[[package]]
name = "nom"
version = "7.1.3"
@@ -1993,6 +2074,50 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
[[package]]
name = "openssl"
version = "0.10.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5"
dependencies = [
"bitflags",
"cfg-if",
"foreign-types",
"libc",
"once_cell",
"openssl-macros",
"openssl-sys",
]
[[package]]
name = "openssl-macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "openssl-probe"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-sys"
version = "0.9.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "opentelemetry"
version = "0.27.1"
@@ -2444,6 +2569,65 @@ version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2"
[[package]]
name = "reqwest"
version = "0.12.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da"
dependencies = [
"base64 0.22.1",
"bytes",
"encoding_rs",
"futures-core",
"futures-util",
"h2 0.4.7",
"http 1.2.0",
"http-body",
"http-body-util",
"hyper",
"hyper-rustls",
"hyper-tls",
"hyper-util",
"ipnet",
"js-sys",
"log",
"mime",
"native-tls",
"once_cell",
"percent-encoding",
"pin-project-lite",
"rustls-pemfile",
"serde",
"serde_json",
"serde_urlencoded",
"sync_wrapper",
"system-configuration",
"tokio",
"tokio-native-tls",
"tower 0.5.2",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"windows-registry",
]
[[package]]
name = "ring"
version = "0.17.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d"
dependencies = [
"cc",
"cfg-if",
"getrandom",
"libc",
"spin",
"untrusted",
"windows-sys 0.52.0",
]
[[package]]
name = "rpassword"
version = "7.3.1"
@@ -2590,13 +2774,17 @@ dependencies = [
"opentelemetry_sdk",
"password-hash",
"pbkdf2",
"quick-xml",
"rand",
"reqwest",
"rpassword",
"rustical_caldav",
"rustical_carddav",
"rustical_dav",
"rustical_frontend",
"rustical_store",
"rustical_store_sqlite",
"rustical_xml",
"serde",
"sqlx",
"tokio",
@@ -2736,6 +2924,7 @@ dependencies = [
"serde",
"sqlx",
"thiserror 2.0.11",
"tokio",
"tracing",
]
@@ -2761,6 +2950,45 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "rustls"
version = "0.23.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f287924602bf649d949c63dc8ac8b235fa5387d394020705b80c4eb597ce5b8"
dependencies = [
"once_cell",
"rustls-pki-types",
"rustls-webpki",
"subtle",
"zeroize",
]
[[package]]
name = "rustls-pemfile"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
dependencies = [
"rustls-pki-types",
]
[[package]]
name = "rustls-pki-types"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37"
[[package]]
name = "rustls-webpki"
version = "0.102.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9"
dependencies = [
"ring",
"rustls-pki-types",
"untrusted",
]
[[package]]
name = "rustversion"
version = "1.0.19"
@@ -2782,12 +3010,44 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "schannel"
version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "security-framework"
version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
dependencies = [
"bitflags",
"core-foundation",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "semver"
version = "1.0.24"
@@ -3231,6 +3491,9 @@ name = "sync_wrapper"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
dependencies = [
"futures-core",
]
[[package]]
name = "synstructure"
@@ -3243,6 +3506,27 @@ dependencies = [
"syn",
]
[[package]]
name = "system-configuration"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
dependencies = [
"bitflags",
"core-foundation",
"system-configuration-sys",
]
[[package]]
name = "system-configuration-sys"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "tempfile"
version = "3.15.0"
@@ -3393,6 +3677,26 @@ dependencies = [
"syn",
]
[[package]]
name = "tokio-native-tls"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
dependencies = [
"native-tls",
"tokio",
]
[[package]]
name = "tokio-rustls"
version = "0.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37"
dependencies = [
"rustls",
"tokio",
]
[[package]]
name = "tokio-stream"
version = "0.1.17"
@@ -3511,6 +3815,7 @@ dependencies = [
"futures-util",
"pin-project-lite",
"sync_wrapper",
"tokio",
"tower-layer",
"tower-service",
]
@@ -3687,6 +3992,12 @@ version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "url"
version = "2.5.4"
@@ -3800,6 +4111,19 @@ dependencies = [
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.49"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2"
dependencies = [
"cfg-if",
"js-sys",
"once_cell",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.99"
@@ -3829,6 +4153,16 @@ version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6"
[[package]]
name = "web-sys"
version = "0.3.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "web-time"
version = "1.1.0"
@@ -3889,6 +4223,36 @@ dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-registry"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0"
dependencies = [
"windows-result",
"windows-strings",
"windows-targets 0.52.6",
]
[[package]]
name = "windows-result"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e"
dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-strings"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10"
dependencies = [
"windows-result",
"windows-targets 0.52.6",
]
[[package]]
name = "windows-sys"
version = "0.48.0"

View File

@@ -133,3 +133,7 @@ rpassword.workspace = true
argon2.workspace = true
pbkdf2.workspace = true
password-hash.workspace = true
reqwest = "0.12.12"
rustical_xml.workspace = true
rustical_dav.workspace = true
quick-xml.workspace = true

View File

@@ -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>,
}

View File

@@ -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,
}

View File

@@ -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>,
}

View File

@@ -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>;

View File

@@ -7,6 +7,7 @@ repository.workspace = true
publish = false
[dependencies]
tokio.workspace = true
rustical_store = { workspace = true }
async-trait = { workspace = true }
serde = { workspace = true }

View File

@@ -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)?;
}
};
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(())
}

View File

@@ -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!(

View File

@@ -1,17 +1,22 @@
use crate::config::Config;
use actix_web::http::KeepAlive;
use actix_web::http::{KeepAlive, StatusCode};
use actix_web::HttpServer;
use anyhow::Result;
use app::make_app;
use clap::{Parser, Subcommand};
use commands::{cmd_gen_config, cmd_pwhash};
use config::{DataStoreConfig, SqliteDataStoreConfig};
use rustical_dav::xml::multistatus::PropstatElement;
use rustical_store::auth::StaticUserStore;
use rustical_store::{AddressbookStore, CalendarStore, SubscriptionStore};
use rustical_store::{AddressbookStore, CalendarStore, CollectionOperation, SubscriptionStore};
use rustical_store_sqlite::calendar_store::SqliteCalendarStore;
use rustical_store_sqlite::{create_db_pool, SqliteStore};
use rustical_xml::{XmlRootTag, XmlSerialize, XmlSerializeRoot};
use setup_tracing::setup_tracing;
use std::fs;
use std::sync::Arc;
use tokio::sync::mpsc::Receiver;
use tracing::{error, info};
mod app;
mod commands;
@@ -43,20 +48,43 @@ async fn get_data_stores(
Arc<dyn AddressbookStore>,
Arc<dyn CalendarStore>,
Arc<dyn SubscriptionStore>,
Receiver<CollectionOperation>,
)> {
Ok(match &config {
DataStoreConfig::Sqlite(SqliteDataStoreConfig { db_url }) => {
let db = create_db_pool(db_url, migrate).await?;
let sqlite_store = Arc::new(SqliteStore::new(db));
(
sqlite_store.clone(),
sqlite_store.clone(),
sqlite_store.clone(),
)
// Channel to watch for changes (for DAV Push)
let (send, recv) = tokio::sync::mpsc::channel(1000);
let addressbook_store = Arc::new(SqliteStore::new(db.clone()));
let cal_store = Arc::new(SqliteCalendarStore::new(db.clone(), send));
let subscription_store = Arc::new(SqliteStore::new(db.clone()));
(addressbook_store, cal_store, subscription_store, recv)
}
})
}
// TODO: Move this code somewhere else :)
#[derive(XmlSerialize, Debug)]
struct PushMessageProp {
#[xml(ns = "rustical_dav::namespace::NS_DAV")]
topic: String,
#[xml(ns = "rustical_dav::namespace::NS_DAV")]
sync_token: Option<String>,
}
#[derive(XmlSerialize, XmlRootTag, Debug)]
#[xml(root = b"push-message", ns = "rustical_dav::namespace::NS_DAVPUSH")]
#[xml(ns_prefix(
rustical_dav::namespace::NS_DAVPUSH = b"",
rustical_dav::namespace::NS_DAV = b"D",
))]
struct PushMessage {
#[xml(ns = "rustical_dav::namespace::NS_DAV")]
propstat: PropstatElement<PushMessageProp>,
}
#[tokio::main]
async fn main() -> Result<()> {
let args = Args::parse();
@@ -69,9 +97,59 @@ async fn main() -> Result<()> {
setup_tracing(&config.tracing);
let (addr_store, cal_store, subscription_store) =
let (addr_store, cal_store, subscription_store, mut update_recv) =
get_data_stores(!args.no_migrations, &config.data_store).await?;
let subscription_store_clone = subscription_store.clone();
tokio::spawn(async move {
let subscription_store = subscription_store_clone.clone();
while let Some(message) = update_recv.recv().await {
dbg!(&message);
if let Ok(subscribers) =
subscription_store.get_subscriptions(&message.topic).await
{
let status = match message.r#type {
rustical_store::CollectionOperationType::Object => StatusCode::OK,
rustical_store::CollectionOperationType::Delete => {
StatusCode::NOT_FOUND
}
};
let push_message = PushMessage {
propstat: PropstatElement {
prop: PushMessageProp {
topic: message.topic,
sync_token: message.sync_token,
},
status,
},
};
let mut output: Vec<_> =
b"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n".into();
let mut writer = quick_xml::Writer::new_with_indent(&mut output, b' ', 4);
if let Err(err) = push_message.serialize_root(&mut writer) {
error!("Could not serialize push message: {}", err);
continue;
}
let payload = String::from_utf8(output).unwrap();
for subscriber in subscribers {
info!(
"Sending a push message to {}: {}",
subscriber.push_resource, payload
);
let client = reqwest::Client::new();
if let Err(err) = client
.post(subscriber.push_resource)
.body(payload.to_owned())
.send()
.await
{
error!("{err}");
}
}
}
}
});
let user_store = Arc::new(match config.auth {
config::AuthConfig::Static(config) => StaticUserStore::new(config),
});