use docbox_database::{
DbErr, DbPool,
models::{
file::File,
folder::{Folder, ResolvedFolder},
link::Link,
},
};
use futures::{FutureExt, Stream, future::BoxFuture};
use std::{collections::VecDeque, task::Poll};
use thiserror::Error;
#[derive(Debug)]
pub enum FolderWalkItem {
Folder(Folder),
File(File),
Link(Link),
}
enum FolderWalkStackItem<'db> {
Unresolved(Folder),
Resolving {
folder: Folder,
future: BoxFuture<'db, Result<ResolvedFolder, DbErr>>,
},
Resolved(FolderWalkItem),
}
#[derive(Debug, Error)]
pub enum FolderWalkError {
#[error("failed to resolve folder")]
ResolveFolder(DbErr),
}
pub struct FolderWalkStream<'db> {
db: &'db DbPool,
stack: VecDeque<FolderWalkStackItem<'db>>,
}
impl<'db> FolderWalkStream<'db> {
pub fn new(db: &'db DbPool, folder: Folder) -> FolderWalkStream<'db> {
let mut stack = VecDeque::new();
stack.push_back(FolderWalkStackItem::Unresolved(folder));
FolderWalkStream { db, stack }
}
}
impl<'db> Stream for FolderWalkStream<'db> {
type Item = Result<FolderWalkItem, FolderWalkError>;
fn poll_next(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> Poll<Option<Self::Item>> {
let this = self.get_mut();
while let Some(item) = this.stack.pop_front() {
match item {
FolderWalkStackItem::Resolving { folder, mut future } => {
let resolved = match future.poll_unpin(cx) {
Poll::Ready(Ok(value)) => value,
Poll::Ready(Err(error)) => {
return Poll::Ready(Some(Err(FolderWalkError::ResolveFolder(error))));
}
Poll::Pending => {
this.stack
.push_front(FolderWalkStackItem::Resolving { folder, future });
return Poll::Pending;
}
};
this.stack
.push_front(FolderWalkStackItem::Resolved(FolderWalkItem::Folder(
folder,
)));
for item in resolved.folders {
this.stack.push_front(FolderWalkStackItem::Unresolved(item));
}
for item in resolved.files {
this.stack
.push_front(FolderWalkStackItem::Resolved(FolderWalkItem::File(item)));
}
for item in resolved.links {
this.stack
.push_front(FolderWalkStackItem::Resolved(FolderWalkItem::Link(item)));
}
}
FolderWalkStackItem::Unresolved(folder) => {
let future = Box::pin(ResolvedFolder::resolve(this.db, folder.id));
this.stack
.push_front(FolderWalkStackItem::Resolving { folder, future });
}
FolderWalkStackItem::Resolved(item) => {
return Poll::Ready(Some(Ok(item)));
}
}
}
Poll::Ready(None)
}
}