qubit_fs/directory/async_directory_stream.rs
1// =============================================================================
2// Copyright (c) 2026 Haixing Hu.
3//
4// SPDX-License-Identifier: Apache-2.0
5//
6// Licensed under the Apache License, Version 2.0.
7// =============================================================================
8// facade tests.
9//! Concrete asynchronous directory stream handle.
10
11use std::fmt::Debug;
12use std::fmt::Formatter;
13use std::fmt::Result as FmtResult;
14use std::time::Instant;
15
16use crate::directory::DirectoryStreamState;
17use crate::directory::ListOptions;
18use crate::directory::ListScope;
19use crate::directory::internal::ListStreamPolicy;
20use crate::error::FsResult;
21use crate::metadata::DirEntry;
22use crate::metadata::FileSystemLimits;
23use crate::spi::AsyncDirectoryStreamSession;
24use crate::spi::SpiFuture;
25
26/// Type-erased asynchronous directory enumeration handle.
27///
28/// The stream validates each provider entry before yielding it and preserves
29/// terminal state across asynchronous calls.
30///
31/// # Examples
32///
33/// ```
34/// # use qubit_fs::{AsyncFileSystem, FsResult, Path};
35/// # use qubit_fs::directory::ListOptions;
36/// # async fn visit(filesystem: &AsyncFileSystem, root: &Path) -> FsResult<()> {
37/// let mut stream = filesystem.list(&qubit_fs::directory::ListScope::Path(root.clone()), ListOptions::default()).await?;
38/// while let Some(entry) = stream.next_entry_async().await? {
39/// println!("{}", entry.path);
40/// }
41/// # Ok(())
42/// # }
43/// ```
44pub struct AsyncDirectoryStream {
45 /// Provider enumeration session.
46 session: Box<dyn AsyncDirectoryStreamSession>,
47 /// Shared validation, deadline, and terminal-state policy.
48 policy: ListStreamPolicy,
49}
50
51impl AsyncDirectoryStream {
52 /// Wraps an already-open asynchronous provider enumeration session.
53 ///
54 /// # Parameters
55 /// - `session`: Provider asynchronous enumeration session.
56 ///
57 /// # Returns
58 /// A concrete type-erased asynchronous directory stream.
59 #[inline]
60 pub(crate) fn new(
61 scope: ListScope,
62 session: Box<dyn AsyncDirectoryStreamSession>,
63 options: ListOptions,
64 provider: &str,
65 path_semantics: crate::path::PathSemantics,
66 limits: FileSystemLimits,
67 ) -> FsResult<Self> {
68 let policy = ListStreamPolicy::new(scope, options, provider, path_semantics, limits, Instant::now())?;
69 Ok(Self { session, policy })
70 }
71
72 /// Returns the current lifecycle state of this stream.
73 #[inline]
74 #[must_use = "inspect the stream lifecycle state"]
75 pub const fn state(&self) -> DirectoryStreamState {
76 self.policy.state()
77 }
78
79 /// Asynchronously reads the next directory entry.
80 ///
81 /// # Returns
82 /// A future resolving to one entry or `None` at end of enumeration.
83 pub fn next_entry_async(&mut self) -> SpiFuture<'_, FsResult<Option<DirEntry>>> {
84 Box::pin(async move {
85 self.policy.before_next(Instant::now())?;
86 let result = self.session.next_entry_async().await;
87 self.policy.finish_next(result, Instant::now())
88 })
89 }
90}
91
92impl Debug for AsyncDirectoryStream {
93 #[inline]
94 fn fmt(&self, formatter: &mut Formatter<'_>) -> FmtResult {
95 formatter.debug_struct("AsyncDirectoryStream").finish_non_exhaustive()
96 }
97}