sos_backend/
compact.rs

1//! Compact folders.
2use crate::{BackendEventLog, Error, FolderEventLog, Result};
3use sos_core::{
4    events::{
5        patch::{FolderDiff, Patch},
6        EventLog, EventLogType, EventRecord,
7    },
8    AccountId, VaultId,
9};
10use sos_database::{
11    entity::{
12        AccountEntity, AccountRow, FolderEntity, FolderRecord, FolderRow,
13    },
14    open_memory, EventLogOwner,
15};
16use sos_filesystem::FolderEventLog as FsFolderEventLog;
17use sos_reducers::FolderReducer;
18use sos_vault::Vault;
19use tempfile::NamedTempFile;
20
21/// Compact a folder event log.
22pub async fn compact_folder(
23    account_id: &AccountId,
24    folder_id: &VaultId,
25    event_log: &mut FolderEventLog,
26) -> Result<()> {
27    match event_log {
28        BackendEventLog::Database(event_log) => {
29            // Get the reduced set of events
30            let events = FolderReducer::new()
31                .reduce(event_log)
32                .await?
33                .compact()
34                .await?;
35
36            // Apply them to a temporary event log file so we
37            // can compute a checkpoint for the diff
38            let client = open_memory().await?;
39
40            // Ensure the foreign key constrains exist
41            // in the temporary database
42            let temp_name = "compact_temp";
43            let account_row =
44                AccountRow::new_insert(account_id, temp_name.to_owned())?;
45            let mut vault = Vault::default();
46            *vault.header_mut().id_mut() = *folder_id;
47            let folder_row = FolderRow::new_insert(&vault).await?;
48            let db_folder_id = *folder_id;
49
50            let folder_row = client
51                .conn(move |conn| {
52                    let account_entity = AccountEntity::new(&conn);
53                    let folder_entity = FolderEntity::new(&conn);
54                    let account_id = account_entity.insert(&account_row)?;
55                    folder_entity.insert_folder(account_id, &folder_row)?;
56                    folder_entity.find_one(&db_folder_id)
57                })
58                .await
59                .map_err(sos_database::Error::from)?;
60            let folder_record = FolderRecord::from_row(folder_row).await?;
61
62            // Copy the event log using the new temporary owner
63            let mut temp_event_log = event_log.with_new_client(
64                client,
65                Some(EventLogOwner::Folder(*account_id, folder_record)),
66            );
67            temp_event_log.apply(events.as_slice()).await?;
68
69            let mut records = Vec::new();
70            for event in &events {
71                records.push(EventRecord::encode_event(event).await?);
72            }
73
74            let checkpoint = temp_event_log
75                .tree()
76                .proof(&[temp_event_log.tree().len() - 1])?;
77
78            let diff = FolderDiff::new(Patch::new(records), checkpoint, None);
79            event_log.replace_all_events(&diff).await?;
80
81            Ok(())
82        }
83        BackendEventLog::FileSystem(event_log) => {
84            // Get the reduced set of events
85            let events = FolderReducer::new()
86                .reduce(event_log)
87                .await?
88                .compact()
89                .await?;
90
91            // Apply them to a temporary event log file
92            let temp = NamedTempFile::new()?;
93            let mut temp_event_log = FsFolderEventLog::<Error>::new_folder(
94                temp.path(),
95                *account_id,
96                EventLogType::Folder(*folder_id),
97            )
98            .await?;
99            temp_event_log.apply(events.as_slice()).await?;
100
101            let mut records = Vec::new();
102            for event in &events {
103                records.push(EventRecord::encode_event(event).await?);
104            }
105
106            let diff = FolderDiff::new(
107                Patch::new(records),
108                temp_event_log
109                    .tree()
110                    .proof(&[temp_event_log.tree().len() - 1])?,
111                None,
112            );
113
114            event_log.replace_all_events(&diff).await?;
115
116            temp.close()?;
117
118            Ok(())
119        }
120    }
121}