Skip to main content

docbox_core/folders/
folder_stream.rs

1use docbox_database::{
2    DbErr, DbPool,
3    models::{
4        file::File,
5        folder::{Folder, ResolvedFolder},
6        link::Link,
7    },
8};
9use futures::{FutureExt, Stream, future::BoxFuture};
10use std::{collections::VecDeque, task::Poll};
11use thiserror::Error;
12
13/// Item produced by [FolderWalkStream]
14#[derive(Debug)]
15pub enum FolderWalkItem {
16    Folder(Folder),
17    File(File),
18    Link(Link),
19}
20
21/// Item within the resolution stack for the folder walk stream
22enum FolderWalkStackItem<'db> {
23    /// Unresolved folder yet to be processed
24    Unresolved(Folder),
25    /// Folder resolve operating that is currently undergoing
26    Resolving {
27        /// The folder
28        folder: Folder,
29        /// Current future for resolving the contents of the folder
30        future: BoxFuture<'db, Result<ResolvedFolder, DbErr>>,
31    },
32    /// Resolved item that can be returned
33    Resolved(FolderWalkItem),
34}
35
36/// Error that can occur when walking the folder
37#[derive(Debug, Error)]
38pub enum FolderWalkError {
39    #[error("failed to resolve folder")]
40    ResolveFolder(DbErr),
41}
42
43/// Stream for walking a folder starting with a top most folder
44/// producing each of the folders children walking depth first
45///
46/// This stream will always produce the deepest content first
47/// in order of links -> files -> folder it is safe to use
48/// this stream to perform deletions of a folder as the deepest
49/// folder contents will always be produced before the folder itself
50pub struct FolderWalkStream<'db> {
51    /// Database pool for resolving folders
52    db: &'db DbPool,
53
54    /// Stack of items to process
55    stack: VecDeque<FolderWalkStackItem<'db>>,
56}
57
58impl<'db> FolderWalkStream<'db> {
59    pub fn new(db: &'db DbPool, folder: Folder) -> FolderWalkStream<'db> {
60        let mut stack = VecDeque::new();
61        stack.push_back(FolderWalkStackItem::Unresolved(folder));
62
63        FolderWalkStream { db, stack }
64    }
65}
66
67impl<'db> Stream for FolderWalkStream<'db> {
68    type Item = Result<FolderWalkItem, FolderWalkError>;
69
70    fn poll_next(
71        self: std::pin::Pin<&mut Self>,
72        cx: &mut std::task::Context<'_>,
73    ) -> Poll<Option<Self::Item>> {
74        let this = self.get_mut();
75
76        while let Some(item) = this.stack.pop_front() {
77            match item {
78                // Currently resolving a folder
79                FolderWalkStackItem::Resolving { folder, mut future } => {
80                    let resolved = match future.poll_unpin(cx) {
81                        // Folder is resolved and can be processed
82                        Poll::Ready(Ok(value)) => value,
83
84                        // Error encountered while iterating
85                        Poll::Ready(Err(error)) => {
86                            return Poll::Ready(Some(Err(FolderWalkError::ResolveFolder(error))));
87                        }
88
89                        // Still waiting for the completed resolve push back onto the stack
90                        Poll::Pending => {
91                            this.stack
92                                .push_front(FolderWalkStackItem::Resolving { folder, future });
93                            return Poll::Pending;
94                        }
95                    };
96
97                    // Folder is now resolved and can be pushed to the stack
98                    this.stack
99                        .push_front(FolderWalkStackItem::Resolved(FolderWalkItem::Folder(
100                            folder,
101                        )));
102
103                    // Nested folders need to be resolved
104                    for item in resolved.folders {
105                        this.stack.push_front(FolderWalkStackItem::Unresolved(item));
106                    }
107
108                    // Files and links can now be resolved
109                    for item in resolved.files {
110                        this.stack
111                            .push_front(FolderWalkStackItem::Resolved(FolderWalkItem::File(item)));
112                    }
113
114                    for item in resolved.links {
115                        this.stack
116                            .push_front(FolderWalkStackItem::Resolved(FolderWalkItem::Link(item)));
117                    }
118                }
119
120                // Next folder to resolve
121                FolderWalkStackItem::Unresolved(folder) => {
122                    let future = Box::pin(ResolvedFolder::resolve(this.db, folder.id));
123                    this.stack
124                        .push_front(FolderWalkStackItem::Resolving { folder, future });
125                }
126
127                // Stack item to return
128                FolderWalkStackItem::Resolved(item) => {
129                    return Poll::Ready(Some(Ok(item)));
130                }
131            }
132        }
133
134        // Reached the end of the content
135        Poll::Ready(None)
136    }
137}