mirror of
https://github.com/lennart-k/rustical.git
synced 2025-12-13 14:42:30 +00:00
Dav Push: Support for calendar collections
This commit is contained in:
364
Cargo.lock
generated
364
Cargo.lock
generated
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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!(
|
||||
|
||||
96
src/main.rs
96
src/main.rs
@@ -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),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user