Add basic sync-token implementation

This commit is contained in:
Lennart
2024-07-28 17:49:15 +02:00
parent a0864d6eeb
commit 33539e8c7a
4 changed files with 121 additions and 10 deletions

View File

@@ -42,6 +42,8 @@ pub enum CalendarPropName {
CurrentUserPrivilegeSet, CurrentUserPrivilegeSet,
MaxResourceSize, MaxResourceSize,
SupportedReportSet, SupportedReportSet,
SyncToken,
Getctag,
} }
#[derive(Debug, Clone, Deserialize, Serialize)] #[derive(Debug, Clone, Deserialize, Serialize)]
@@ -73,6 +75,8 @@ pub enum CalendarProp {
MaxResourceSize(String), MaxResourceSize(String),
CurrentUserPrivilegeSet(UserPrivilegeSet), CurrentUserPrivilegeSet(UserPrivilegeSet),
SupportedReportSet(SupportedReportSet), SupportedReportSet(SupportedReportSet),
SyncToken(String),
Getctag(String),
#[serde(other)] #[serde(other)]
Invalid, Invalid,
} }
@@ -141,6 +145,14 @@ impl Resource for CalendarFile {
CalendarPropName::SupportedReportSet => { CalendarPropName::SupportedReportSet => {
CalendarProp::SupportedReportSet(SupportedReportSet::default()) CalendarProp::SupportedReportSet(SupportedReportSet::default())
} }
CalendarPropName::SyncToken => CalendarProp::SyncToken(format!(
"github.com/lennart-k/rustical/ns/{}",
self.calendar.synctoken
)),
CalendarPropName::Getctag => CalendarProp::Getctag(format!(
"github.com/lennart-k/rustical/ns/{}",
self.calendar.synctoken
)),
}) })
} }
@@ -180,6 +192,8 @@ impl Resource for CalendarFile {
CalendarProp::MaxResourceSize(_) => Err(rustical_dav::Error::PropReadOnly), CalendarProp::MaxResourceSize(_) => Err(rustical_dav::Error::PropReadOnly),
CalendarProp::CurrentUserPrivilegeSet(_) => Err(rustical_dav::Error::PropReadOnly), CalendarProp::CurrentUserPrivilegeSet(_) => Err(rustical_dav::Error::PropReadOnly),
CalendarProp::SupportedReportSet(_) => Err(rustical_dav::Error::PropReadOnly), CalendarProp::SupportedReportSet(_) => Err(rustical_dav::Error::PropReadOnly),
CalendarProp::SyncToken(_) => Err(rustical_dav::Error::PropReadOnly),
CalendarProp::Getctag(_) => Err(rustical_dav::Error::PropReadOnly),
CalendarProp::Invalid => Err(rustical_dav::Error::PropReadOnly), CalendarProp::Invalid => Err(rustical_dav::Error::PropReadOnly),
} }
} }
@@ -217,6 +231,8 @@ impl Resource for CalendarFile {
CalendarPropName::MaxResourceSize => Err(rustical_dav::Error::PropReadOnly), CalendarPropName::MaxResourceSize => Err(rustical_dav::Error::PropReadOnly),
CalendarPropName::CurrentUserPrivilegeSet => Err(rustical_dav::Error::PropReadOnly), CalendarPropName::CurrentUserPrivilegeSet => Err(rustical_dav::Error::PropReadOnly),
CalendarPropName::SupportedReportSet => Err(rustical_dav::Error::PropReadOnly), CalendarPropName::SupportedReportSet => Err(rustical_dav::Error::PropReadOnly),
CalendarPropName::SyncToken => Err(rustical_dav::Error::PropReadOnly),
CalendarPropName::Getctag => Err(rustical_dav::Error::PropReadOnly),
} }
} }
} }

View File

@@ -1,6 +1,7 @@
CREATE TABLE calendars ( CREATE TABLE calendars (
principal TEXT NOT NULL, principal TEXT NOT NULL,
id TEXT NOT NULL, id TEXT NOT NULL,
synctoken INTEGER DEFAULT 0 NOT NULL,
displayname TEXT, displayname TEXT,
description TEXT, description TEXT,
'order' INT DEFAULT 0 NOT NULL, 'order' INT DEFAULT 0 NOT NULL,
@@ -21,3 +22,15 @@ CREATE TABLE events (
FOREIGN KEY (principal, cid) REFERENCES calendars(principal, id) FOREIGN KEY (principal, cid) REFERENCES calendars(principal, id)
); );
CREATE TABLE eventchangelog (
-- The actual sync token is the SQLite field 'ROWID'
principal TEXT NOT NULL,
cid TEXT NOT NULL,
uid TEXT NOT NULL,
operation INTEGER NOT NULL,
synctoken INTEGER DEFAULT 0 NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (principal, cid, created_at),
FOREIGN KEY (principal, cid) REFERENCES calendars(principal, id) ON DELETE CASCADE
)

View File

@@ -11,4 +11,5 @@ pub struct Calendar {
pub color: Option<String>, pub color: Option<String>,
pub timezone: Option<String>, pub timezone: Option<String>,
pub deleted_at: Option<NaiveDateTime>, pub deleted_at: Option<NaiveDateTime>,
pub synctoken: i64,
} }

View File

@@ -1,10 +1,12 @@
use crate::calendar::Calendar;
use crate::event::Event;
use crate::{CalendarStore, Error};
use anyhow::Result; use anyhow::Result;
use async_trait::async_trait; use async_trait::async_trait;
use serde::Serialize;
use sqlx::Transaction;
use sqlx::{sqlite::SqliteConnectOptions, Pool, Sqlite, SqlitePool}; use sqlx::{sqlite::SqliteConnectOptions, Pool, Sqlite, SqlitePool};
use crate::event::Event;
use crate::{calendar::Calendar, CalendarStore, Error};
#[derive(Debug)] #[derive(Debug)]
pub struct SqliteCalendarStore { pub struct SqliteCalendarStore {
db: SqlitePool, db: SqlitePool,
@@ -30,12 +32,55 @@ impl TryFrom<EventRow> for Event {
} }
} }
#[derive(Serialize, sqlx::Type)]
#[serde(rename_all = "kebab-case")]
enum CalendarChangeOperation {
// There's no distinction between Add and Modify
Add,
Delete,
}
// Logs an operation to the events
async fn log_event_operation(
tx: &mut Transaction<'_, Sqlite>,
principal: &str,
cid: &str,
uid: &str,
operation: CalendarChangeOperation,
) -> Result<(), Error> {
sqlx::query!(
r#"
UPDATE calendars
SET synctoken = synctoken + 1
WHERE (principal, id) = (?1, ?2)"#,
principal,
cid
)
.execute(&mut **tx)
.await?;
sqlx::query!(
r#"
INSERT INTO eventchangelog (principal, cid, uid, operation, synctoken)
VALUES (?1, ?2, ?3, ?4, (
SELECT synctoken FROM calendars WHERE (principal, id) = (?1, ?2)
))"#,
principal,
cid,
uid,
operation
)
.execute(&mut **tx)
.await?;
Ok(())
}
#[async_trait] #[async_trait]
impl CalendarStore for SqliteCalendarStore { impl CalendarStore for SqliteCalendarStore {
async fn get_calendar(&self, principal: &str, id: &str) -> Result<Calendar, Error> { async fn get_calendar(&self, principal: &str, id: &str) -> Result<Calendar, Error> {
let cal = sqlx::query_as!( let cal = sqlx::query_as!(
Calendar, Calendar,
r#"SELECT principal, id, "order", displayname, description, color, timezone, deleted_at r#"SELECT principal, id, synctoken, "order", displayname, description, color, timezone, deleted_at
FROM calendars FROM calendars
WHERE (principal, id) = (?, ?)"#, WHERE (principal, id) = (?, ?)"#,
principal, principal,
@@ -49,7 +94,7 @@ impl CalendarStore for SqliteCalendarStore {
async fn get_calendars(&self, principal: &str) -> Result<Vec<Calendar>, Error> { async fn get_calendars(&self, principal: &str) -> Result<Vec<Calendar>, Error> {
let cals = sqlx::query_as!( let cals = sqlx::query_as!(
Calendar, Calendar,
r#"SELECT principal, id, displayname, "order", description, color, timezone, deleted_at r#"SELECT principal, id, synctoken, displayname, "order", description, color, timezone, deleted_at
FROM calendars FROM calendars
WHERE principal = ? AND deleted_at IS NULL"#, WHERE principal = ? AND deleted_at IS NULL"#,
principal principal
@@ -176,7 +221,10 @@ impl CalendarStore for SqliteCalendarStore {
uid: String, uid: String,
ics: String, ics: String,
) -> Result<(), Error> { ) -> Result<(), Error> {
let _ = Event::from_ics(uid.to_owned(), ics.to_owned())?; let mut tx = self.db.begin().await?;
// input validation
Event::from_ics(uid.to_owned(), ics.to_owned())?;
sqlx::query!( sqlx::query!(
"REPLACE INTO events (principal, cid, uid, ics) VALUES (?, ?, ?, ?)", "REPLACE INTO events (principal, cid, uid, ics) VALUES (?, ?, ?, ?)",
principal, principal,
@@ -184,8 +232,18 @@ impl CalendarStore for SqliteCalendarStore {
uid, uid,
ics, ics,
) )
.execute(&self.db) .execute(&mut *tx)
.await?; .await?;
log_event_operation(
&mut tx,
&principal,
&cid,
&uid,
CalendarChangeOperation::Add,
)
.await?;
tx.commit().await?;
Ok(()) Ok(())
} }
@@ -196,6 +254,8 @@ impl CalendarStore for SqliteCalendarStore {
uid: &str, uid: &str,
use_trashbin: bool, use_trashbin: bool,
) -> Result<(), Error> { ) -> Result<(), Error> {
let mut tx = self.db.begin().await?;
match use_trashbin { match use_trashbin {
true => { true => {
sqlx::query!( sqlx::query!(
@@ -204,27 +264,48 @@ impl CalendarStore for SqliteCalendarStore {
cid, cid,
uid uid
) )
.execute(&self.db) .execute(&mut *tx)
.await?; .await?;
} }
false => { false => {
sqlx::query!("DELETE FROM events WHERE cid = ? AND uid = ?", cid, uid) sqlx::query!("DELETE FROM events WHERE cid = ? AND uid = ?", cid, uid)
.execute(&self.db) .execute(&mut *tx)
.await?; .await?;
} }
}; };
log_event_operation(
&mut tx,
principal,
cid,
uid,
CalendarChangeOperation::Delete,
)
.await?;
tx.commit().await?;
Ok(()) Ok(())
} }
async fn restore_event(&mut self, principal: &str, cid: &str, uid: &str) -> Result<(), Error> { async fn restore_event(&mut self, principal: &str, cid: &str, uid: &str) -> Result<(), Error> {
let mut tx = self.db.begin().await?;
sqlx::query!( sqlx::query!(
r#"UPDATE events SET deleted_at = NULL, updated_at = datetime() WHERE (principal, cid, uid) = (?, ?, ?)"#, r#"UPDATE events SET deleted_at = NULL, updated_at = datetime() WHERE (principal, cid, uid) = (?, ?, ?)"#,
principal, principal,
cid, cid,
uid uid
) )
.execute(&self.db) .execute(&mut *tx)
.await?; .await?;
log_event_operation(
&mut tx,
principal,
cid,
uid,
CalendarChangeOperation::Delete,
)
.await?;
tx.commit().await?;
Ok(()) Ok(())
} }
} }