Skip to main content

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}