Merge branch 'main' into feature/birthday-calendar

This commit is contained in:
Lennart
2025-11-22 18:30:44 +01:00
34 changed files with 649 additions and 241 deletions

View File

@@ -9,7 +9,7 @@ use rustical_store::{
};
use sqlx::{Acquire, Executor, Sqlite, SqlitePool, Transaction};
use tokio::sync::mpsc::Sender;
use tracing::{error, instrument};
use tracing::{error, instrument, warn};
pub mod birthday_calendar;
@@ -34,6 +34,60 @@ pub struct SqliteAddressbookStore {
}
impl SqliteAddressbookStore {
// Commit "orphaned" objects to the changelog table
pub async fn repair_orphans(&self) -> Result<(), Error> {
struct Row {
principal: String,
addressbook_id: String,
id: String,
deleted: bool,
}
let mut tx = self
.db
.begin_with(BEGIN_IMMEDIATE)
.await
.map_err(crate::Error::from)?;
let rows = sqlx::query_as!(
Row,
r#"
SELECT principal, addressbook_id, id, (deleted_at IS NOT NULL) AS "deleted: bool"
FROM addressobjects
WHERE (principal, addressbook_id, id) NOT IN (
SELECT DISTINCT principal, addressbook_id, object_id FROM addressobjectchangelog
)
;
"#,
)
.fetch_all(&mut *tx)
.await
.map_err(crate::Error::from)?;
for row in rows {
let operation = if row.deleted {
ChangeOperation::Delete
} else {
ChangeOperation::Add
};
warn!(
"Commiting orphaned addressbook object ({},{},{}), deleted={}",
&row.principal, &row.addressbook_id, &row.id, &row.deleted
);
log_object_operation(
&mut tx,
&row.principal,
&row.addressbook_id,
&row.id,
operation,
)
.await?;
}
tx.commit().await.map_err(crate::Error::from)?;
Ok(())
}
async fn _get_addressbook<'e, E: Executor<'e, Database = Sqlite>>(
executor: E,
principal: &str,
@@ -92,9 +146,9 @@ impl SqliteAddressbookStore {
async fn _update_addressbook<'e, E: Executor<'e, Database = Sqlite>>(
executor: E,
principal: String,
id: String,
addressbook: Addressbook,
principal: &str,
id: &str,
addressbook: &Addressbook,
) -> Result<(), rustical_store::Error> {
let result = sqlx::query!(
r#"UPDATE addressbooks SET principal = ?, id = ?, displayname = ?, description = ?, push_topic = ?
@@ -399,7 +453,7 @@ impl AddressbookStore for SqliteAddressbookStore {
id: String,
addressbook: Addressbook,
) -> Result<(), rustical_store::Error> {
Self::_update_addressbook(&self.db, principal, id, addressbook).await
Self::_update_addressbook(&self.db, &principal, &id, &addressbook).await
}
#[instrument]
@@ -631,7 +685,7 @@ impl AddressbookStore for SqliteAddressbookStore {
.await?
.push_topic,
}) {
error!("Push notification about deleted addressbook failed: {err}");
error!("Push notification about restored addressbook object failed: {err}");
}
Ok(())
@@ -665,6 +719,7 @@ impl AddressbookStore for SqliteAddressbookStore {
Self::_insert_addressbook(&mut *tx, &addressbook).await?;
}
let mut sync_token = None;
for object in objects {
Self::_put_object(
&mut *tx,
@@ -674,9 +729,31 @@ impl AddressbookStore for SqliteAddressbookStore {
false,
)
.await?;
sync_token = Some(
log_object_operation(
&mut tx,
&addressbook.principal,
&addressbook.id,
object.get_id(),
ChangeOperation::Add,
)
.await?,
);
}
tx.commit().await.map_err(crate::Error::from)?;
if let Some(sync_token) = sync_token
&& let Err(err) = self.sender.try_send(CollectionOperation {
data: CollectionOperationInfo::Content { sync_token },
topic: self
.get_addressbook(&addressbook.principal, &addressbook.id, true)
.await?
.push_topic,
})
{
error!("Push notification about imported addressbook failed: {err}");
}
Ok(())
}
}
@@ -688,7 +765,7 @@ async fn log_object_operation(
addressbook_id: &str,
object_id: &str,
operation: ChangeOperation,
) -> Result<String, sqlx::Error> {
) -> Result<String, Error> {
struct Synctoken {
synctoken: i64,
}
@@ -703,7 +780,8 @@ async fn log_object_operation(
addressbook_id
)
.fetch_one(&mut **tx)
.await?;
.await
.map_err(crate::Error::from)?;
sqlx::query!(
r#"
@@ -717,6 +795,7 @@ async fn log_object_operation(
operation
)
.execute(&mut **tx)
.await?;
.await
.map_err(crate::Error::from)?;
Ok(format_synctoken(synctoken))
}