Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gio: add FileEnumerator::into_stream #1035

Merged
merged 1 commit into from Feb 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions gio/Gir.toml
Expand Up @@ -792,6 +792,7 @@ manual_traits = ["FileDescriptorBasedExtManual"]
[[object]]
name = "Gio.FileEnumerator"
status = "generate"
manual_traits = ["FileEnumeratorExtManual"]
[[object.function]]
name = "iterate"
# better with rust iterators
Expand Down
126 changes: 124 additions & 2 deletions gio/src/file_enumerator.rs
@@ -1,8 +1,9 @@
// Take a look at the license at the top of the repository in the LICENSE file.

use std::iter::FusedIterator;

use crate::{prelude::*, FileEnumerator, FileInfo};
use futures_core::future::LocalBoxFuture;
use futures_util::FutureExt;
use std::{iter::FusedIterator, task::Poll};

impl Iterator for FileEnumerator {
type Item = Result<FileInfo, glib::Error>;
Expand All @@ -16,3 +17,124 @@ impl Iterator for FileEnumerator {
}

impl FusedIterator for FileEnumerator {}

pub trait FileEnumeratorExtManual {
fn into_stream(self, num_files: i32, priority: glib::Priority) -> FileEnumeratorStream;
}

impl<O: IsA<FileEnumerator>> FileEnumeratorExtManual for O {
// rustdoc-stripper-ignore-next
/// Converts the enumerator into a [`Stream`](futures_core::Stream).
fn into_stream(self, num_files: i32, priority: glib::Priority) -> FileEnumeratorStream {
let future = Some(self.next_files_future(num_files, priority));
FileEnumeratorStream {
enumerator: self.upcast(),
future,
num_files,
priority,
}
}
}

// rustdoc-stripper-ignore-next
/// A [`Stream`](futures_core::Stream) used to enumerate files in directories.
pub struct FileEnumeratorStream {
enumerator: FileEnumerator,
future: Option<LocalBoxFuture<'static, Result<Vec<FileInfo>, glib::Error>>>,
num_files: i32,
priority: glib::Priority,
}

impl std::fmt::Debug for FileEnumeratorStream {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FileEnumeratorStream")
.field("enumerator", &self.enumerator)
.field("num_files", &self.num_files)
.field("priority", &self.priority)
.finish()
}
}

impl futures_core::Stream for FileEnumeratorStream {
type Item = Result<Vec<FileInfo>, glib::Error>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not a glib::List<FileInfo>?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tbh, I'd leave glib::List to API that interface directly with C. This is a Rust convenience, so I'd recommend using native/idiomatic Rust type wherever possible.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

glib::List works as an Iterator so that for the majority of uses it should be equivalent except for not having to copy over everything into a Vec.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just passing the vec from the underlying next_files_future, it is already converted by the time the stream gets it. It should probably be changed in gir to use List, and then it can be changed here

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


#[inline]
fn poll_next(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> Poll<Option<Self::Item>> {
match self.future.take() {
Some(mut f) => match f.poll_unpin(cx) {
Poll::Ready(Ok(fs)) if fs.is_empty() => Poll::Ready(None),
Poll::Ready(Ok(fs)) => {
self.future = Some(
self.enumerator
.next_files_future(self.num_files, self.priority),
);
Poll::Ready(Some(Ok(fs)))
}
Poll::Ready(Err(e)) => Poll::Ready(Some(Err(e))),
Poll::Pending => {
self.future = Some(f);
Poll::Pending
}
},
None => Poll::Ready(None),
}
}
}

impl futures_core::FusedStream for FileEnumeratorStream {
#[inline]
fn is_terminated(&self) -> bool {
self.future.is_none()
}
}

#[cfg(test)]
mod tests {
use crate::prelude::*;
use futures_util::StreamExt;
use std::{cell::Cell, rc::Rc};
#[test]
fn file_enumerator_stream() {
let dir = std::env::current_dir().unwrap();
let ctx = glib::MainContext::new();
let lp = glib::MainLoop::new(Some(&ctx), false);
let res = Rc::new(Cell::new(None));

let lp_clone = lp.clone();
let res_clone = res.clone();
ctx.spawn_local(async move {
res_clone.replace(Some(
async {
let dir = crate::File::for_path(dir);
let mut stream = dir
.enumerate_children_future(
crate::FILE_ATTRIBUTE_STANDARD_NAME,
crate::FileQueryInfoFlags::NONE,
glib::Priority::default(),
)
.await?
.into_stream(4, glib::Priority::default());
while let Some(files) = stream.next().await {
for file in files? {
let _ = file.name();
}
}
Ok::<_, glib::Error>(())
}
.await,
));
lp_clone.quit();
});
lp.run();
// propagate any error from the future into a panic
Rc::try_unwrap(res)
.unwrap_or_else(|_| panic!("future not finished"))
.into_inner()
.unwrap()
.unwrap();
}
}
1 change: 1 addition & 0 deletions gio/src/lib.rs
Expand Up @@ -48,6 +48,7 @@ mod file_descriptor_based;
#[cfg(any(unix, feature = "dox"))]
pub use file_descriptor_based::FileDescriptorBased;
mod file_enumerator;
pub use crate::file_enumerator::FileEnumeratorStream;
mod file_info;
mod flags;
mod inet_address;
Expand Down
3 changes: 2 additions & 1 deletion gio/src/prelude.rs
Expand Up @@ -29,7 +29,8 @@ pub use crate::unix_socket_address::{UnixSocketAddressExtManual, UnixSocketAddre
pub use crate::{
action_map::ActionMapExtManual, application::*, auto::traits::*, cancellable::*, converter::*,
data_input_stream::DataInputStreamExtManual, datagram_based::*, dbus_proxy::DBusProxyExtManual,
file::FileExtManual, inet_address::InetAddressExtManual, input_stream::InputStreamExtManual,
file::FileExtManual, file_enumerator::FileEnumeratorExtManual,
inet_address::InetAddressExtManual, input_stream::InputStreamExtManual,
io_stream::IOStreamExtManual, list_model::ListModelExtManual,
output_stream::OutputStreamExtManual, pollable_input_stream::PollableInputStreamExtManual,
pollable_output_stream::PollableOutputStreamExtManual, settings::SettingsExtManual,
Expand Down