Undelete: deleted files, calendar events, contacts and Sieve scripts are kept and restored where they were (UD-1, UD-8 to UD-11)
Files, events and contacts are noted when deleted for good and archived by the unindex task when retention is on; Sieve scripts are archived at deletion. A restore goes back to its folder, calendar or address book if it still exists, takes a free " (restored)" name, comes back inactive for scripts, and is refused over quota with the item left archived. Acceptance tests 6, 8 and 12.
This commit is contained in:
@@ -11,6 +11,7 @@ types = { path = "../types" }
|
||||
trc = { path = "../trc" }
|
||||
nlp = { path = "../nlp" }
|
||||
registry = { path = "../registry" }
|
||||
inbuxa-features = { path = "../features" }
|
||||
calcard = { version = "0.3", features = ["rkyv"] }
|
||||
hashify = "0.2"
|
||||
rkyv = { version = "0.8.18", features = ["little_endian"] }
|
||||
|
||||
@@ -462,6 +462,15 @@ impl DestroyArchive<Archive<&ArchivedCalendarEvent>> {
|
||||
batch: &mut BatchBuilder,
|
||||
) -> trc::Result<()> {
|
||||
let event = self.0;
|
||||
// inbuxa: UD-1: noted for undelete
|
||||
crate::inbuxa::note_event(
|
||||
batch,
|
||||
account_id,
|
||||
document_id,
|
||||
&event
|
||||
.deserialize::<CalendarEvent>()
|
||||
.caused_by(trc::location!())?,
|
||||
)?;
|
||||
// Delete event
|
||||
batch
|
||||
.with_account_id(account_id)
|
||||
|
||||
@@ -234,6 +234,15 @@ impl DestroyArchive<Archive<&ArchivedContactCard>> {
|
||||
)
|
||||
.caused_by(trc::location!())?;
|
||||
} else {
|
||||
// inbuxa: UD-1: noted for undelete
|
||||
crate::inbuxa::note_card(
|
||||
batch,
|
||||
account_id,
|
||||
document_id,
|
||||
&card
|
||||
.deserialize::<ContactCard>()
|
||||
.caused_by(trc::location!())?,
|
||||
)?;
|
||||
// Delete card
|
||||
batch
|
||||
.with_document(document_id)
|
||||
@@ -262,6 +271,16 @@ impl DestroyArchive<Archive<&ArchivedContactCard>> {
|
||||
document_id: u32,
|
||||
batch: &mut BatchBuilder,
|
||||
) -> trc::Result<()> {
|
||||
// inbuxa: UD-1: noted for undelete
|
||||
crate::inbuxa::note_card(
|
||||
batch,
|
||||
account_id,
|
||||
document_id,
|
||||
&self
|
||||
.0
|
||||
.deserialize::<ContactCard>()
|
||||
.caused_by(trc::location!())?,
|
||||
)?;
|
||||
batch
|
||||
.with_account_id(account_id)
|
||||
.with_collection(Collection::ContactCard)
|
||||
|
||||
@@ -82,6 +82,14 @@ impl DestroyArchive<Archive<&ArchivedFileNode>> {
|
||||
batch: &mut BatchBuilder,
|
||||
path: String,
|
||||
) -> trc::Result<()> {
|
||||
// inbuxa: UD-1: noted for undelete
|
||||
crate::inbuxa::note_file(
|
||||
batch,
|
||||
account_id,
|
||||
document_id,
|
||||
&self.0.deserialize::<FileNode>().caused_by(trc::location!())?,
|
||||
)?;
|
||||
|
||||
// Prepare write batch
|
||||
batch
|
||||
.with_account_id(account_id)
|
||||
@@ -143,6 +151,16 @@ impl DestroyArchive<Vec<u32>> {
|
||||
))
|
||||
.await?
|
||||
{
|
||||
// inbuxa: UD-1: noted for undelete
|
||||
crate::inbuxa::note_file(
|
||||
batch,
|
||||
account_id,
|
||||
document_id,
|
||||
&node
|
||||
.deserialize::<FileNode>()
|
||||
.caused_by(trc::location!())?,
|
||||
)?;
|
||||
|
||||
// Delete record
|
||||
batch
|
||||
.with_document(document_id)
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Coffey Labs
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
//! Undelete notes for files, calendar events and contacts deleted for good
|
||||
//! (`docs/spec/features/undelete.md`, UD-1). The rules are in
|
||||
//! `inbuxa_features::undelete`.
|
||||
|
||||
use crate::{calendar::CalendarEvent, contact::ContactCard, file::FileNode};
|
||||
use inbuxa_features::undelete::{
|
||||
data::Extra,
|
||||
groupware::{Kind, Note, note},
|
||||
};
|
||||
use registry::schema::{
|
||||
enums::IndexDocumentType,
|
||||
structs::{Task, TaskIndexDocument, TaskStatus},
|
||||
};
|
||||
use store::write::BatchBuilder;
|
||||
|
||||
/// A file (not a folder) deleted for good.
|
||||
pub fn note_file(
|
||||
batch: &mut BatchBuilder,
|
||||
account_id: u32,
|
||||
document_id: u32,
|
||||
node: &FileNode,
|
||||
) -> trc::Result<()> {
|
||||
let Some(file) = &node.file else {
|
||||
return Ok(());
|
||||
};
|
||||
// Files aren't search-indexed, so nothing else schedules the task that
|
||||
// archives the note: schedule it here
|
||||
batch.schedule_task(Task::UnindexDocument(TaskIndexDocument {
|
||||
account_id: account_id.into(),
|
||||
document_id: document_id.into(),
|
||||
document_type: IndexDocumentType::File,
|
||||
status: TaskStatus::now(),
|
||||
}));
|
||||
note(
|
||||
batch,
|
||||
Kind::File,
|
||||
account_id,
|
||||
document_id,
|
||||
Note {
|
||||
deleted_at: 0,
|
||||
created_at: node.created,
|
||||
content: None,
|
||||
blob_hash: Some(file.blob_hash.as_slice().to_vec()),
|
||||
extra: Extra::FileNode {
|
||||
parent_id: Some(node.parent_id),
|
||||
name: node.name.clone(),
|
||||
media_type: file.media_type.clone(),
|
||||
size: file.size,
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// A calendar event deleted for good.
|
||||
pub fn note_event(
|
||||
batch: &mut BatchBuilder,
|
||||
account_id: u32,
|
||||
document_id: u32,
|
||||
event: &CalendarEvent,
|
||||
) -> trc::Result<()> {
|
||||
note(
|
||||
batch,
|
||||
Kind::CalendarEvent,
|
||||
account_id,
|
||||
document_id,
|
||||
Note {
|
||||
deleted_at: 0,
|
||||
created_at: event.created,
|
||||
content: Some(event.data.event.to_string()),
|
||||
blob_hash: None,
|
||||
extra: Extra::CalendarEvent {
|
||||
calendar_ids: event.names.iter().map(|n| n.parent_id).collect(),
|
||||
name: event.names.first().map(|n| n.name.clone()).unwrap_or_default(),
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// A contact deleted for good.
|
||||
pub fn note_card(
|
||||
batch: &mut BatchBuilder,
|
||||
account_id: u32,
|
||||
document_id: u32,
|
||||
card: &ContactCard,
|
||||
) -> trc::Result<()> {
|
||||
note(
|
||||
batch,
|
||||
Kind::ContactCard,
|
||||
account_id,
|
||||
document_id,
|
||||
Note {
|
||||
deleted_at: 0,
|
||||
created_at: card.created,
|
||||
content: Some(card.card.to_string()),
|
||||
blob_hash: None,
|
||||
extra: Extra::ContactCard {
|
||||
address_book_ids: card.names.iter().map(|n| n.parent_id).collect(),
|
||||
name: card.names.first().map(|n| n.name.clone()).unwrap_or_default(),
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -16,6 +16,7 @@ pub mod cache;
|
||||
pub mod calendar;
|
||||
pub mod contact;
|
||||
pub mod file;
|
||||
pub mod inbuxa; // inbuxa: undelete notes
|
||||
pub mod scheduling;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
|
||||
Reference in New Issue
Block a user