gio/
file_enumerator.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use crate::{prelude::*, FileEnumerator, FileInfo};
4use futures_core::future::LocalBoxFuture;
5use futures_util::FutureExt;
6use glib::translate::{from_glib, from_glib_full, ToGlibPtr};
7use std::{iter::FusedIterator, task::Poll};
8
9impl Iterator for FileEnumerator {
10    type Item = Result<FileInfo, glib::Error>;
11
12    fn next(&mut self) -> Option<Result<FileInfo, glib::Error>> {
13        match self.next_file(crate::Cancellable::NONE) {
14            Err(err) => Some(Err(err)),
15            Ok(file_info) => file_info.map(Ok),
16        }
17    }
18}
19
20impl FusedIterator for FileEnumerator {}
21
22pub trait FileEnumeratorExtManual: IsA<FileEnumerator> {
23    // rustdoc-stripper-ignore-next
24    /// Converts the enumerator into a [`Stream`](futures_core::Stream).
25    fn into_stream(self, num_files: i32, priority: glib::Priority) -> FileEnumeratorStream {
26        let future = Some(self.next_files_future(num_files, priority));
27        FileEnumeratorStream {
28            enumerator: self.upcast(),
29            future,
30            num_files,
31            priority,
32        }
33    }
34
35    #[doc(alias = "g_file_enumerator_close")]
36    fn close(
37        &self,
38        cancellable: Option<&impl IsA<crate::Cancellable>>,
39    ) -> (bool, Option<glib::Error>) {
40        unsafe {
41            let mut error = std::ptr::null_mut();
42            let ret = crate::ffi::g_file_enumerator_close(
43                self.as_ref().to_glib_none().0,
44                cancellable.map(|p| p.as_ref()).to_glib_none().0,
45                &mut error,
46            );
47            (from_glib(ret), from_glib_full(error))
48        }
49    }
50}
51
52impl<O: IsA<FileEnumerator>> FileEnumeratorExtManual for O {}
53
54// rustdoc-stripper-ignore-next
55/// A [`Stream`](futures_core::Stream) used to enumerate files in directories.
56pub struct FileEnumeratorStream {
57    enumerator: FileEnumerator,
58    future: Option<LocalBoxFuture<'static, Result<Vec<FileInfo>, glib::Error>>>,
59    num_files: i32,
60    priority: glib::Priority,
61}
62
63impl std::fmt::Debug for FileEnumeratorStream {
64    #[inline]
65    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66        f.debug_struct("FileEnumeratorStream")
67            .field("enumerator", &self.enumerator)
68            .field("num_files", &self.num_files)
69            .field("priority", &self.priority)
70            .finish()
71    }
72}
73
74impl futures_core::Stream for FileEnumeratorStream {
75    type Item = Result<Vec<FileInfo>, glib::Error>;
76
77    #[inline]
78    fn poll_next(
79        mut self: std::pin::Pin<&mut Self>,
80        cx: &mut std::task::Context<'_>,
81    ) -> Poll<Option<Self::Item>> {
82        match self.future.take() {
83            Some(mut f) => match f.poll_unpin(cx) {
84                Poll::Ready(Ok(fs)) if fs.is_empty() => Poll::Ready(None),
85                Poll::Ready(Ok(fs)) => {
86                    self.future = Some(
87                        self.enumerator
88                            .next_files_future(self.num_files, self.priority),
89                    );
90                    Poll::Ready(Some(Ok(fs)))
91                }
92                Poll::Ready(Err(e)) => Poll::Ready(Some(Err(e))),
93                Poll::Pending => {
94                    self.future = Some(f);
95                    Poll::Pending
96                }
97            },
98            None => Poll::Ready(None),
99        }
100    }
101}
102
103impl futures_core::FusedStream for FileEnumeratorStream {
104    #[inline]
105    fn is_terminated(&self) -> bool {
106        self.future.is_none()
107    }
108}
109
110#[cfg(test)]
111mod tests {
112    use crate::prelude::*;
113    use futures_util::StreamExt;
114    use std::{cell::Cell, rc::Rc};
115    #[test]
116    fn file_enumerator_stream() {
117        let dir = std::env::current_dir().unwrap();
118        let ctx = glib::MainContext::new();
119        let lp = glib::MainLoop::new(Some(&ctx), false);
120        let res = Rc::new(Cell::new(None));
121
122        let lp_clone = lp.clone();
123        let res_clone = res.clone();
124        ctx.spawn_local(async move {
125            res_clone.replace(Some(
126                async {
127                    let dir = crate::File::for_path(dir);
128                    let mut stream = dir
129                        .enumerate_children_future(
130                            crate::FILE_ATTRIBUTE_STANDARD_NAME,
131                            crate::FileQueryInfoFlags::NONE,
132                            glib::Priority::default(),
133                        )
134                        .await?
135                        .into_stream(4, glib::Priority::default());
136                    while let Some(files) = stream.next().await {
137                        for file in files? {
138                            let _ = file.name();
139                        }
140                    }
141                    Ok::<_, glib::Error>(())
142                }
143                .await,
144            ));
145            lp_clone.quit();
146        });
147        lp.run();
148        // propagate any error from the future into a panic
149        Rc::try_unwrap(res)
150            .unwrap_or_else(|_| panic!("future not finished"))
151            .into_inner()
152            .unwrap()
153            .unwrap();
154    }
155}