docbox_core/folders/
delete_folder.rs

1use crate::files::delete_file::delete_file;
2use crate::links::delete_link::delete_link;
3use crate::{
4    events::{TenantEventMessage, TenantEventPublisher},
5    storage::TenantStorageLayer,
6};
7use docbox_database::{
8    DbPool,
9    models::{
10        document_box::WithScope,
11        file::File,
12        folder::{Folder, ResolvedFolder},
13        link::Link,
14    },
15};
16use docbox_search::TenantSearchIndex;
17use std::collections::VecDeque;
18
19/// Item to be removed
20pub enum RemoveStackItem {
21    /// Folder that needs to have its children removed
22    Folder(Folder),
23    /// Folder that has already been processed, all children
24    /// should have been removed by previous stack passes
25    EmptyFolder(Folder),
26    /// File to remove
27    File(File),
28    /// Link to remove
29    Link(Link),
30}
31
32pub async fn delete_folder(
33    db: &DbPool,
34    storage: &TenantStorageLayer,
35    search: &TenantSearchIndex,
36    events: &TenantEventPublisher,
37    folder: Folder,
38) -> anyhow::Result<()> {
39    // Stack to store the next item to delete
40    let mut stack = VecDeque::new();
41
42    let document_box = folder.document_box.clone();
43
44    // Push the first folder item
45    stack.push_back(RemoveStackItem::Folder(folder));
46
47    while let Some(item) = stack.pop_front() {
48        match item {
49            RemoveStackItem::Folder(folder) => {
50                // Resolve the folder children
51                let resolved = ResolvedFolder::resolve(db, &folder).await?;
52
53                // Push the empty folder first (Will be taken out last)
54                stack.push_front(RemoveStackItem::EmptyFolder(folder));
55
56                // Populate the stack with the resolved files, links, and folders
57                for item in resolved.folders {
58                    stack.push_front(RemoveStackItem::Folder(item));
59                }
60
61                for item in resolved.files {
62                    stack.push_front(RemoveStackItem::File(item));
63                }
64
65                for item in resolved.links {
66                    stack.push_front(RemoveStackItem::Link(item));
67                }
68            }
69            RemoveStackItem::EmptyFolder(folder) => {
70                internal_delete_folder(db, search, events, folder).await?;
71            }
72            RemoveStackItem::File(file) => {
73                delete_file(db, storage, search, events, file, document_box.clone()).await?;
74            }
75            RemoveStackItem::Link(link) => {
76                delete_link(db, search, events, link, document_box.clone()).await?;
77            }
78        }
79    }
80
81    Ok(())
82}
83
84/// Deletes the folder itself and associated metadata, use [delete_folder]
85/// to properly delete the folder and all of its recursive contents
86async fn internal_delete_folder(
87    db: &DbPool,
88    search: &TenantSearchIndex,
89    events: &TenantEventPublisher,
90    folder: Folder,
91) -> anyhow::Result<()> {
92    // Delete the indexed file contents
93    search.delete_data(folder.id).await?;
94
95    let result = folder
96        .delete(db)
97        .await
98        .inspect_err(|error| tracing::error!(?error, "failed to delete folder"))?;
99
100    let document_box = folder.document_box.clone();
101
102    // Check we actually removed something before emitting an event
103    if result.rows_affected() < 1 {
104        return Ok(());
105    }
106
107    // Publish an event
108    events.publish_event(TenantEventMessage::FolderDeleted(WithScope::new(
109        folder,
110        document_box,
111    )));
112
113    Ok(())
114}