qubit_fs/directory/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//! Concrete synchronous directory stream handle.
9
10use std::fmt::Debug;
11use std::fmt::Formatter;
12use std::fmt::Result as FmtResult;
13use std::time::Instant;
14
15use crate::directory::DirectoryStreamState;
16use crate::directory::ListOptions;
17use crate::directory::ListScope;
18use crate::directory::internal::ListStreamPolicy;
19use crate::error::FsResult;
20use crate::metadata::DirEntry;
21use crate::metadata::FileSystemLimits;
22use crate::spi::DirectoryStreamSpi;
23
24/// Type-erased synchronous directory enumeration handle.
25///
26/// The stream validates every provider entry against the requested root and
27/// listing options before returning it to the caller.
28///
29/// # Examples
30///
31/// This helper demonstrates the normal bounded-listing workflow without
32/// requiring a concrete provider in the documentation build.
33///
34/// ```
35/// # use qubit_fs::{FileSystem, FsResult, Path};
36/// # use qubit_fs::directory::ListOptions;
37/// # fn visit(filesystem: &FileSystem, root: &Path) -> FsResult<()> {
38/// let mut stream = filesystem.list(&qubit_fs::directory::ListScope::Path(root.clone()), ListOptions::default())?;
39/// while let Some(entry) = stream.next_entry()? {
40/// println!("{}", entry.path);
41/// }
42/// # Ok(())
43/// # }
44/// ```
45pub struct DirectoryStream {
46 /// Provider enumeration session.
47 session: Box<dyn DirectoryStreamSpi>,
48 /// Shared validation, deadline, and terminal-state policy.
49 policy: ListStreamPolicy,
50}
51
52impl DirectoryStream {
53 /// Wraps an already-open provider enumeration session.
54 ///
55 /// # Parameters
56 /// - `session`: Provider directory enumeration session.
57 ///
58 /// # Returns
59 /// A concrete type-erased directory stream.
60 #[inline]
61 pub(crate) fn new(
62 scope: ListScope,
63 session: Box<dyn DirectoryStreamSpi>,
64 options: ListOptions,
65 provider: &str,
66 path_semantics: crate::path::PathSemantics,
67 limits: FileSystemLimits,
68 ) -> FsResult<Self> {
69 let policy = ListStreamPolicy::new(scope, options, provider, path_semantics, limits, Instant::now())?;
70 Ok(Self { session, policy })
71 }
72
73 /// Returns the current lifecycle state of this stream.
74 #[inline]
75 #[must_use = "inspect the stream lifecycle state"]
76 pub const fn state(&self) -> DirectoryStreamState {
77 self.policy.state()
78 }
79
80 /// Reads the next directory entry.
81 ///
82 /// # Returns
83 /// `Some` for one entry or `None` at end of enumeration.
84 ///
85 /// # Errors
86 /// Returns a filesystem error when enumeration cannot continue.
87 #[inline]
88 pub fn next_entry(&mut self) -> FsResult<Option<DirEntry>> {
89 self.policy.before_next(Instant::now())?;
90 let result = self.session.next_entry();
91 self.policy.finish_next(result, Instant::now())
92 }
93}
94
95impl Debug for DirectoryStream {
96 #[inline]
97 fn fmt(&self, formatter: &mut Formatter<'_>) -> FmtResult {
98 formatter.debug_struct("DirectoryStream").finish_non_exhaustive()
99 }
100}