From 3ce3935d8288dadc76b6c677ebbdb75fa56a17d9 Mon Sep 17 00:00:00 2001 From: fbrouille Date: Thu, 29 May 2025 15:38:37 +0000 Subject: [PATCH 1/4] Add gio::FileEnumerator subclass Signed-off-by: fbrouille --- gio/src/subclass/file_enumerator.rs | 154 +++++++++++++++ gio/src/subclass/mod.rs | 2 + gio/tests/file_enumerator.rs | 293 ++++++++++++++++++++++++++++ gio/tests/file_utilities/mod.rs | 84 ++++++++ 4 files changed, 533 insertions(+) create mode 100644 gio/src/subclass/file_enumerator.rs create mode 100644 gio/tests/file_enumerator.rs create mode 100644 gio/tests/file_utilities/mod.rs diff --git a/gio/src/subclass/file_enumerator.rs b/gio/src/subclass/file_enumerator.rs new file mode 100644 index 000000000000..44a647523809 --- /dev/null +++ b/gio/src/subclass/file_enumerator.rs @@ -0,0 +1,154 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use glib::{prelude::*, subclass::prelude::*, translate::*, Error}; + +use crate::{ffi, traits::FileEnumeratorExt, Cancellable, FileEnumerator, FileInfo, IOErrorEnum}; + +// Support custom implementation of virtual functions defined in `gio::ffi::GFileEnumeratorClass` except pairs `xxx_async/xxx_finish` for which GIO provides a default implementation. +pub trait FileEnumeratorImpl: ObjectImpl + ObjectSubclass> { + fn next_file(&self, cancellable: Option<&Cancellable>) -> Result, Error> { + self.parent_next_file(cancellable) + } + + fn close(&self, cancellable: Option<&Cancellable>) -> Result<(), Error> { + self.parent_close(cancellable) + } +} + +// Support parent implementation of virtual functions defined in `gio::ffi::GFileEnumeratorClass` except pairs `xxx_async/xxx_finish` for which GIO provides a default implementation. +pub trait FileEnumeratorImplExt: FileEnumeratorImpl { + fn parent_next_file( + &self, + cancellable: Option<&Cancellable>, + ) -> Result, Error> { + if self.obj().is_closed() { + Err(Error::new::( + IOErrorEnum::Closed, + "Enumerator is closed", + )) + } else { + unsafe { + let data = Self::type_data(); + let parent_class = data.as_ref().parent_class() as *const ffi::GFileEnumeratorClass; + + let f = (*parent_class) + .next_file + .expect("No parent class implementation for \"next_file\""); + + let mut error = std::ptr::null_mut(); + let res = f( + self.obj() + .unsafe_cast_ref::() + .to_glib_none() + .0, + cancellable.as_ref().to_glib_none().0, + &mut error, + ); + if error.is_null() { + Ok(from_glib_full(res)) + } else { + Err(from_glib_full(error)) + } + } + } + } + + fn parent_close(&self, cancellable: Option<&Cancellable>) -> Result<(), Error> { + unsafe { + let data = Self::type_data(); + let parent_class = data.as_ref().parent_class() as *const ffi::GFileEnumeratorClass; + + let f = (*parent_class) + .close_fn + .expect("No parent class implementation for \"close_fn\""); + + // get the corresponding object instance without checking the reference count because the object might just be finalized. + let obj = { + let offset = -data.as_ref().impl_offset(); + let ptr = self as *const Self as usize; + let ptr = if offset < 0 { + ptr - (-offset) as usize + } else { + ptr + offset as usize + } as *const ::GlibType; + glib::BorrowedObject::::new(mut_override(ptr)) + }; + + let mut error = std::ptr::null_mut(); + let is_ok = f( + obj.unsafe_cast_ref::().to_glib_none().0, + cancellable.as_ref().to_glib_none().0, + &mut error, + ); + debug_assert_eq!(is_ok == glib::ffi::GFALSE, !error.is_null()); + if error.is_null() { + Ok(()) + } else { + Err(from_glib_full(error)) + } + } + } +} + +impl FileEnumeratorImplExt for T {} + +// Implement virtual functions defined in `gio::ffi::GFileEnumeratorClass` except pairs `xxx_async/xxx_finish` for which GIO provides a default implementation. +unsafe impl IsSubclassable for FileEnumerator { + fn class_init(class: &mut ::glib::Class) { + Self::parent_class_init::(class); + + let klass = class.as_mut(); + klass.next_file = Some(next_file::); + klass.close_fn = Some(close_fn::); + // `GFileEnumerator` already implements `xxx_async/xxx_finish` vfuncs and this should be ok. + // TODO: when needed, override the `GFileEnumerator` implementation of the following vfuncs: + // klass.next_files_async = Some(next_files_async::); + // klass.next_files_finish = Some(next_files_finish::); + // klass.close_async = Some(close_async::); + // klass.close_finish = Some(close_finish::); + } +} + +unsafe extern "C" fn next_file( + enumerator: *mut ffi::GFileEnumerator, + cancellable: *mut ffi::GCancellable, + error: *mut *mut glib::ffi::GError, +) -> *mut ffi::GFileInfo { + let instance = &*(enumerator as *mut T::Instance); + let imp = instance.imp(); + let cancellable = Option::::from_glib_none(cancellable); + + let res = imp.next_file(cancellable.as_ref()); + + match res { + Ok(fileinfo) => fileinfo.to_glib_full(), + Err(err) => { + if !error.is_null() { + *error = err.to_glib_full() + } + std::ptr::null_mut() + } + } +} + +unsafe extern "C" fn close_fn( + enumerator: *mut ffi::GFileEnumerator, + cancellable: *mut ffi::GCancellable, + error: *mut *mut glib::ffi::GError, +) -> glib::ffi::gboolean { + let instance = &*(enumerator as *mut T::Instance); + let imp = instance.imp(); + let cancellable = Option::::from_glib_none(cancellable); + + let res = imp.close(cancellable.as_ref()); + + match res { + Ok(_) => true.into_glib(), + Err(err) => { + if !error.is_null() { + *error = err.to_glib_full() + } + false.into_glib() + } + } +} diff --git a/gio/src/subclass/mod.rs b/gio/src/subclass/mod.rs index cbf526ef248a..609e3d57c64e 100644 --- a/gio/src/subclass/mod.rs +++ b/gio/src/subclass/mod.rs @@ -4,6 +4,7 @@ mod action_group; mod action_map; mod application; mod async_initable; +mod file_enumerator; mod file_monitor; mod initable; mod input_stream; @@ -24,6 +25,7 @@ pub mod prelude { action_map::{ActionMapImpl, ActionMapImplExt}, application::{ApplicationImpl, ApplicationImplExt}, async_initable::{AsyncInitableImpl, AsyncInitableImplExt}, + file_enumerator::{FileEnumeratorImpl, FileEnumeratorImplExt}, file_monitor::{FileMonitorImpl, FileMonitorImplExt}, initable::{InitableImpl, InitableImplExt}, input_stream::{InputStreamImpl, InputStreamImplExt}, diff --git a/gio/tests/file_enumerator.rs b/gio/tests/file_enumerator.rs new file mode 100644 index 000000000000..5d29a8b7c0da --- /dev/null +++ b/gio/tests/file_enumerator.rs @@ -0,0 +1,293 @@ +// Take a look at the license at the top of the repository in the LICENSE file. +// +// The following tests rely on a custom type `MyLocalFileEnumerator` that extends the existing GIO type `GLocalFileEnumerator`. +// For each virtual method defined in class `gio::ffi::GFileEnumeratorClass`, a test checks that `MyLocalFileEnumerator` and `GLocalFileEnumerator` return the same results. +// Note that a `MyLocalFileEnumerator` instance is built explicitly by calling `glib::Object::builder` whereas a a `GLocalFileEnumerator` instance is created by calling `gio::auto::File::for_path`. + +use gio::{ + prelude::*, subclass::prelude::*, Cancellable, File, FileAttributeMatcher, FileEnumerator, + FileInfo, FileQueryInfoFlags, IOErrorEnum, +}; +use glib::translate::{IntoGlib, ToGlibPtr}; + +// Binding of existing GIO type GLocalFileEnumerator. +pub mod ffi { + #[cfg(not(windows))] + use libc::DIR; + use libc::{c_char, c_int, dev_t, ino_t}; + + use gio::ffi; + + #[derive(Copy, Clone)] + #[repr(C)] + pub struct GLocalParentFileInfo { + pub writable: glib::ffi::gboolean, + pub is_sticky: glib::ffi::gboolean, + pub has_trash_dir: glib::ffi::gboolean, + pub owner: c_int, + pub device: dev_t, + pub inode: ino_t, + pub extra_data: glib::ffi::gpointer, + pub free_extra_data: glib::ffi::GDestroyNotify, + } + + #[cfg(not(windows))] + #[repr(C)] + pub struct DirEntry { + _data: [u8; 0], + _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, + } + + #[derive(Copy, Clone)] + #[repr(C)] + pub struct GLocalFileEnumerator { + pub parent_instance: ffi::GFileEnumerator, + pub matcher: *mut ffi::GFileAttributeMatcher, + pub reduced_matcher: *mut ffi::GFileAttributeMatcher, + pub filename: *mut c_char, + pub attributes: *mut c_char, + pub flags: ffi::GFileQueryInfoFlags, + pub got_parent_info: glib::ffi::gboolean, + pub parent_info: GLocalParentFileInfo, + #[cfg(windows)] + pub dir: *mut glib::ffi::GDir, + #[cfg(not(windows))] + pub dir: *mut DIR, + #[cfg(not(windows))] + pub entries: *mut DirEntry, + #[cfg(not(windows))] + pub entries_pos: c_int, + #[cfg(not(windows))] + pub at_end: glib::ffi::gboolean, + pub follow_symlinks: glib::ffi::gboolean, + } + + #[derive(Copy, Clone)] + #[repr(C)] + pub struct GLocalFileEnumeratorClass { + pub parent_class: ffi::GFileEnumeratorClass, + } +} + +glib::wrapper! { + #[doc(alias = "GLocalFileEnumerator")] + pub struct LocalFileEnumerator(Object) @extends FileEnumerator; + + match fn { + type_ => || { + use std::sync::Once; + static ONCE: Once = Once::new(); + + // ensure type is initialized by calling `gio::auto::File::for_path` to create a `GLocalFile` instance + // and then by calling `gio::auto::file::FileExt::monitor_file` to create a `GLocalFileMonitor` instance. + ONCE.call_once(|| unsafe { + use crate::{File, FileQueryInfoFlags}; + let _ = File::for_path("/").enumerate_children("*", FileQueryInfoFlags::NONE, Cancellable::NONE); + }); + glib::gobject_ffi::g_type_from_name("GLocalFileEnumerator".to_glib_none().0) + }, + } +} + +pub trait LocalFileEnumeratorImpl: + ObjectImpl + ObjectSubclass + IsA> +{ +} + +unsafe impl IsSubclassable + for LocalFileEnumerator +{ +} + +// Define `MyLocalFileEnumerator` as a subclass of `GLocalFileEnumerator`. +mod imp { + #[cfg(not(windows))] + use libc::opendir; + + use crate::FileAttributeMatcher; + + use super::*; + + #[derive(Default)] + pub struct MyLocalFileEnumerator; + + #[glib::object_subclass] + impl ObjectSubclass for MyLocalFileEnumerator { + const NAME: &'static str = "MyLocalFileEnumerator"; + type Type = super::MyLocalFileEnumerator; + type ParentType = LocalFileEnumerator; + } + + // Handle properties `container`, `matcher` and `flags` to properly initialize `GLocalFileEnumerator` fields `dir`, `filename`, `matcher` and `flags` in the parent instance at creation time. + impl glib::subclass::object::DerivedObjectProperties for MyLocalFileEnumerator { + fn derived_properties() -> &'static [glib::ParamSpec] { + use glib::prelude::ParamSpecBuilderExt; + static PROPERTIES: ::std::sync::OnceLock<[glib::ParamSpec; 3]> = + ::std::sync::OnceLock::new(); + PROPERTIES.get_or_init(||[ + < ::Value as glib::prelude::HasParamSpec> ::param_spec_builder()("container").write_only().build(), + < ::Value as glib::prelude::HasParamSpec> ::param_spec_builder()("matcher").write_only().build(), + < ::Value as glib::prelude::HasParamSpec> ::param_spec_builder()("flags").write_only().build(), + ]) + } + + fn derived_property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { + panic!("missing getter for property {}", pspec.name()) + } + + #[allow(unreachable_code)] + fn derived_set_property(&self, id: usize, value: &glib::Value, pspec: &glib::ParamSpec) { + let obj = self.obj(); + let parent = obj.upcast_ref::(); + let glocal_file_enumerator = ::GlibType, + >>::to_glib_none(parent); + match id { + 1 => { + let file = value.get::().unwrap(); + let filename = file.path().unwrap(); + unsafe { + #[cfg(windows)] + { + (*glocal_file_enumerator.0).dir = glib::ffi::g_dir_open( + filename.to_glib_none().0, + 0, + std::ptr::null_mut(), + ); + } + #[cfg(not(windows))] + { + (*glocal_file_enumerator.0).dir = opendir(filename.to_glib_none().0); + } + (*glocal_file_enumerator.0).filename = filename.as_path().to_glib_full(); + } + } + 2 => { + let matcher = value.get::().unwrap(); + unsafe { + (*glocal_file_enumerator.0).matcher = matcher.to_glib_full(); + } + } + 3 => { + let flags = value.get::().unwrap(); + unsafe { + (*glocal_file_enumerator.0).flags = flags.into_glib(); + } + } + _ => panic!("missing setter for property {}", pspec.name()), + } + } + } + + #[glib::derived_properties] + impl ObjectImpl for MyLocalFileEnumerator {} + + // Implements `FileEnumeratorImpl` with default implementation, which calls the parent's implementation. + impl FileEnumeratorImpl for MyLocalFileEnumerator {} + + impl LocalFileEnumeratorImpl for MyLocalFileEnumerator {} +} + +glib::wrapper! { + pub struct MyLocalFileEnumerator(ObjectSubclass) @extends LocalFileEnumerator, FileEnumerator; +} + +impl Iterator for MyLocalFileEnumerator { + type Item = Result; + + fn next(&mut self) -> Option> { + match self.imp().next_file(crate::Cancellable::NONE) { + Err(err) => Some(Err(err)), + Ok(file_info) => file_info.map(Ok), + } + } +} + +#[allow(dead_code)] +mod file_utilities; +use file_utilities::Temp; + +#[test] +fn file_enumerator_next_file() { + // temporary dir and file are deleted when variables go out of scope + let my_temp_dir = Temp::make_dir("next_file_XXXXXX"); + let _my_temp_file = my_temp_dir.create_file_child("my_file_XXXXXX"); + let _my_temp_file2 = my_temp_dir.create_file_child("my_file2_XXXXXX"); + + // build a new `MyLocalFileEnumerator` + let mut enumerator = glib::Object::builder::() + .property("container", File::for_path(&my_temp_dir.path)) + .property("matcher", FileAttributeMatcher::new("*")) + .property("flags", FileQueryInfoFlags::NONE) + .build(); + + // build a new `LocalFileEnumerator` + let res = File::for_path(&my_temp_dir.path).enumerate_children( + "*", + FileQueryInfoFlags::NONE, + Cancellable::NONE, + ); + assert!(res.is_ok(), "{}", res.err().unwrap()); + let expected_enumerator = res.unwrap(); + + // iterate over `LocalFileEnumerator` to invoke its implementation of `gio::ffi::GFileEnumeratorClass::next_file` + let mut n = 0; + for res in expected_enumerator { + assert!(res.is_ok(), "{}", res.err().unwrap()); + let expected = res.unwrap(); + + // invoke `MyLocalFileEnumerator` implementation of `gio::ffi::GFileEnumeratorClass::next_file` + let res = enumerator.next(); + assert!(res.is_some(), "unexpected None"); + let res = res.unwrap(); + assert!(res.is_ok(), "{}", res.err().unwrap()); + let file_info = res.unwrap(); + + // both filenames should equal + assert_eq!(file_info.name(), expected.name()); + n += 1; + } + assert_eq!(n, 2); +} + +#[test] +fn file_enumerator_close() { + // temporary dir and file are deleted when variables go out of scope + let my_temp_dir = Temp::make_dir("close_XXXXXX"); + + // build a new `MyLocalFileEnumerator` + let mut enumerator = glib::Object::builder::() + .property("container", File::for_path(&my_temp_dir.path)) + .property("matcher", FileAttributeMatcher::new("*")) + .property("flags", FileQueryInfoFlags::NONE) + .build(); + + // build a new `LocalFileEnumerator` + let res = File::for_path(&my_temp_dir.path).enumerate_children( + "*", + FileQueryInfoFlags::NONE, + Cancellable::NONE, + ); + assert!(res.is_ok(), "{}", res.err().unwrap()); + let mut expected_enumerator = res.unwrap(); + + // invoke `MyLocalFileEnumerator` implementation of `gio::ffi::GFileEnumeratorClass::close` + let res = enumerator.close(Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + + // invoke `LocalFileEnumerator` implementation of `gio::ffi::GFileEnumeratorClass::close` + let res = expected_enumerator.close(Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + + // invoke `MyLocalFileEnumerator` implementation of `gio::ffi::GFileEnumeratorClass::next` + let res = enumerator.next(); + + // invoke `LocalFileEnumerator` implementation of `gio::ffi::GFileEnumeratorClass::next` + let expected = expected_enumerator.next(); + + // both next results should equal + assert_eq!( + res.map(|res| res.map_err(|err| err.kind::())), + expected.map(|res| res.map_err(|err| err.kind::())) + ); +} diff --git a/gio/tests/file_utilities/mod.rs b/gio/tests/file_utilities/mod.rs new file mode 100644 index 000000000000..fac8b60e91b3 --- /dev/null +++ b/gio/tests/file_utilities/mod.rs @@ -0,0 +1,84 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use std::ops::Deref; + +use gio::{prelude::*, Cancellable, File}; +use glib::translate::{FromGlibPtrFull, ToGlibPtr}; + +// Temp is a test utility that creates a new temporary file (or directory) and delete it at drop time. +#[derive(Clone)] +pub struct Temp { + pub file: Option, + pub path: String, + pub basename: String, +} + +impl Temp { + // Make a new temporary directory. + pub fn make_dir(tmpl: &str) -> Self { + unsafe { + let res = glib::ffi::g_dir_make_tmp(tmpl.to_glib_none().0, std::ptr::null_mut()); + assert!(!res.is_null()); + let path = glib::GString::from_glib_full(res).as_str().to_owned(); + let file = File::for_parse_name(&path); + let res = file.basename(); + assert!(res.is_some()); + let basename = res.unwrap().as_path().to_str().unwrap().to_owned(); + Self { + file: Some(file), + path, + basename, + } + } + } + + // Create a new temporary file under a temporary directory. + pub fn create_file_child(&self, tmpl: &str) -> Self { + unsafe { + let tmpl = glib::gformat!("{}/{}", self.path, tmpl); + let fd = glib::ffi::g_mkstemp(tmpl.to_glib_none().0); + assert_ne!(fd, -1, "file not created"); + { + // close file + use std::os::fd::FromRawFd; + let _ = std::fs::File::from_raw_fd(fd); + } + let path = tmpl.as_str().to_owned(); + let file = File::for_parse_name(&path); + let res = file.basename(); + assert!(res.is_some()); + let basename = res.unwrap().as_path().to_str().unwrap().to_owned(); + Self { + file: Some(file), + path, + basename, + } + } + } +} + +impl Deref for Temp { + type Target = Option; + + // Dereference self to the inner temporary file. + fn deref(&self) -> &Self::Target { + &self.file + } +} + +impl Temp { + // Take ownership of the inner file so it won't be deleted when self goes out of scope. + pub fn take_file(&mut self) -> Option { + self.file.take() + } +} + +impl Drop for Temp { + // Delete the inner temporary file (if it has not been taken). + fn drop(&mut self) { + if let Some(ref file) = self.file { + let res = file.delete(Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + } + } +} From 303648a105d16456584e881686fe74d6f84bf846 Mon Sep 17 00:00:00 2001 From: fbrouille Date: Wed, 21 May 2025 09:35:33 +0000 Subject: [PATCH 2/4] Add gio::File subclass Signed-off-by: fbrouille --- gio/src/file_attribute_value.rs | 5 +- gio/src/subclass/file.rs | 3681 +++++++++++++++++++++++++++++++ gio/src/subclass/mod.rs | 2 + gio/tests/file.rs | 1840 +++++++++++++++ gio/tests/file_utilities/mod.rs | 65 +- 5 files changed, 5590 insertions(+), 3 deletions(-) create mode 100644 gio/src/subclass/file.rs create mode 100644 gio/tests/file.rs diff --git a/gio/src/file_attribute_value.rs b/gio/src/file_attribute_value.rs index d87ed2678cdb..b2f9cb8e9221 100644 --- a/gio/src/file_attribute_value.rs +++ b/gio/src/file_attribute_value.rs @@ -76,11 +76,14 @@ impl FileAttributeValue<'_> { pub(crate) fn as_ptr(&self) -> glib::ffi::gpointer { self.0.as_ptr() } + + pub(crate) fn for_pointer(type_: FileAttributeType, value_p: *mut std::ffi::c_void) -> Self { + Self(FileAttributeValueInner::Pointer(type_, value_p)) + } } #[derive(Debug)] pub(crate) enum FileAttributeValueInner<'a> { - #[allow(dead_code)] // TODO remove this allow attribute when Pointer will be used by this crate Pointer(FileAttributeType, glib::ffi::gpointer), String(<&'a str as ToGlibPtr<'a, *mut libc::c_char>>::Storage), ByteString(&'a CStr), diff --git a/gio/src/subclass/file.rs b/gio/src/subclass/file.rs new file mode 100644 index 000000000000..64eb9f2d2a02 --- /dev/null +++ b/gio/src/subclass/file.rs @@ -0,0 +1,3681 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use glib::{ + prelude::*, subclass::prelude::*, thread_guard, translate::*, Error, GString, Interface, Object, +}; + +use crate::{ + ffi, AsyncResult, Cancellable, DriveStartFlags, File, FileAttributeInfoList, + FileAttributeValue, FileCopyFlags, FileCreateFlags, FileEnumerator, FileIOStream, FileInfo, + FileInputStream, FileMeasureFlags, FileMonitor, FileMonitorFlags, FileOutputStream, + FileQueryInfoFlags, IOErrorEnum, Mount, MountMountFlags, MountOperation, MountUnmountFlags, + Task, +}; + +use libc::{c_char, c_uint}; + +use std::{boxed::Box as Box_, path::PathBuf}; + +// Support custom implementation of virtual functions defined in `gio::ffi::GFileIface` except pairs `xxx_async/xxx_finish` for which GIO provides a default implementation. +pub trait FileImpl: ObjectImpl + ObjectSubclass> { + const SUPPORT_THREAD_CONTEXT: bool = true; + + fn dup(&self) -> File { + self.parent_dup() + } + + fn hash(&self) -> u32 { + self.parent_hash() + } + + fn equal(&self, file2: &File) -> bool { + self.parent_equal(file2) + } + + fn is_native(&self) -> bool { + self.parent_is_native() + } + + fn has_uri_scheme(&self, uri_scheme: &str) -> bool { + self.parent_has_uri_scheme(uri_scheme) + } + + fn uri_scheme(&self) -> Option { + self.parent_uri_scheme() + } + + fn basename(&self) -> Option { + self.parent_basename() + } + + fn path(&self) -> Option { + self.parent_path() + } + + fn uri(&self) -> String { + self.parent_uri() + } + + fn parse_name(&self) -> String { + self.parent_parse_name() + } + + fn parent(&self) -> Option { + self.parent_parent() + } + + fn has_prefix(&self, prefix: &File) -> bool { + self.parent_has_prefix(prefix) + } + + fn relative_path(&self, descendant: &File) -> Option { + self.parent_relative_path(descendant) + } + + fn resolve_relative_path(&self, relative_path: impl AsRef) -> File { + self.parent_resolve_relative_path(relative_path) + } + + fn child_for_display_name(&self, display_name: &str) -> Result { + self.parent_child_for_display_name(display_name) + } + + fn enumerate_children( + &self, + attributes: &str, + flags: FileQueryInfoFlags, + cancellable: Option<&impl IsA>, + ) -> Result { + self.parent_enumerate_children(attributes, flags, cancellable) + } + + fn query_info( + &self, + attributes: &str, + flags: FileQueryInfoFlags, + cancellable: Option<&impl IsA>, + ) -> Result { + self.parent_query_info(attributes, flags, cancellable) + } + + fn query_filesystem_info( + &self, + attributes: &str, + cancellable: Option<&impl IsA>, + ) -> Result { + self.parent_query_filesystem_info(attributes, cancellable) + } + + fn find_enclosing_mount( + &self, + cancellable: Option<&impl IsA>, + ) -> Result { + self.parent_find_enclosing_mount(cancellable) + } + + fn set_display_name( + &self, + display_name: &str, + cancellable: Option<&impl IsA>, + ) -> Result { + self.parent_set_display_name(display_name, cancellable) + } + + fn query_settable_attributes( + &self, + cancellable: Option<&impl IsA>, + ) -> Result { + self.parent_query_settable_attributes(cancellable) + } + + fn query_writable_namespaces( + &self, + cancellable: Option<&impl IsA>, + ) -> Result { + self.parent_query_writable_namespaces(cancellable) + } + + fn set_attribute<'a>( + &self, + attribute: &str, + value: impl Into>, + flags: FileQueryInfoFlags, + cancellable: Option<&impl IsA>, + ) -> Result<(), Error> { + self.parent_set_attribute(attribute, value, flags, cancellable) + } + + fn set_attributes_from_info( + &self, + info: &FileInfo, + flags: FileQueryInfoFlags, + cancellable: Option<&impl IsA>, + ) -> Result<(), Error> { + self.parent_set_attributes_from_info(info, flags, cancellable) + } + + fn read_fn( + &self, + cancellable: Option<&impl IsA>, + ) -> Result { + self.parent_read_fn(cancellable) + } + + fn append_to( + &self, + flags: FileCreateFlags, + cancellable: Option<&impl IsA>, + ) -> Result { + self.parent_append_to(flags, cancellable) + } + + fn create( + &self, + flags: FileCreateFlags, + cancellable: Option<&impl IsA>, + ) -> Result { + self.parent_create(flags, cancellable) + } + + fn replace( + &self, + etag: Option<&str>, + make_backup: bool, + flags: FileCreateFlags, + cancellable: Option<&impl IsA>, + ) -> Result { + self.parent_replace(etag, make_backup, flags, cancellable) + } + + fn delete(&self, cancellable: Option<&impl IsA>) -> Result<(), Error> { + self.parent_delete(cancellable) + } + + fn trash(&self, cancellable: Option<&impl IsA>) -> Result<(), Error> { + self.parent_trash(cancellable) + } + + fn make_directory(&self, cancellable: Option<&impl IsA>) -> Result<(), Error> { + self.parent_make_directory(cancellable) + } + + fn make_symbolic_link( + &self, + symlink_value: impl AsRef, + cancellable: Option<&impl IsA>, + ) -> Result<(), Error> { + self.parent_make_symbolic_link(symlink_value, cancellable) + } + + fn copy( + source: &File, + destination: &File, + flags: FileCopyFlags, + cancellable: Option<&impl IsA>, + progress_callback: Option<&mut dyn (FnMut(i64, i64))>, + ) -> Result<(), Error> { + Self::parent_copy(source, destination, flags, cancellable, progress_callback) + } + + fn move_( + source: &File, + destination: &File, + flags: FileCopyFlags, + cancellable: Option<&impl IsA>, + progress_callback: Option<&mut dyn (FnMut(i64, i64))>, + ) -> Result<(), Error> { + Self::parent_move(source, destination, flags, cancellable, progress_callback) + } + + fn mount_mountable( + &self, + flags: MountMountFlags, + mount_operation: Option<&MountOperation>, + cancellable: Option<&impl IsA>, + callback: Option

, + ) { + self.parent_mount_mountable(flags, mount_operation, cancellable, callback) + } + + fn mount_mountable_finish(&self, res: &AsyncResult) -> Result { + self.parent_mount_mountable_finish(res) + } + + fn unmount_mountable( + &self, + flags: MountUnmountFlags, + cancellable: Option<&impl IsA>, + callback: Option

, + ) { + self.parent_unmount_mountable(flags, cancellable, callback) + } + + fn unmount_mountable_finish(&self, res: &AsyncResult) -> Result<(), Error> { + self.parent_unmount_mountable_finish(res) + } + + fn eject_mountable( + &self, + flags: MountUnmountFlags, + cancellable: Option<&impl IsA>, + callback: Option

, + ) { + self.parent_eject_mountable(flags, cancellable, callback) + } + + fn eject_mountable_finish(&self, res: &AsyncResult) -> Result<(), Error> { + self.parent_eject_mountable_finish(res) + } + + fn mount_enclosing_volume( + &self, + flags: MountMountFlags, + mount_operation: Option<&MountOperation>, + cancellable: Option<&impl IsA>, + callback: Option

, + ) { + self.parent_mount_enclosing_volume(flags, mount_operation, cancellable, callback) + } + + fn mount_enclosing_volume_finish(&self, res: &AsyncResult) -> Result<(), Error> { + self.parent_mount_enclosing_volume_finish(res) + } + + fn monitor_dir( + &self, + flags: FileMonitorFlags, + cancellable: Option<&impl IsA>, + ) -> Result { + self.parent_monitor_dir(flags, cancellable) + } + + fn monitor_file( + &self, + flags: FileMonitorFlags, + cancellable: Option<&impl IsA>, + ) -> Result { + self.parent_monitor_file(flags, cancellable) + } + + fn open_readwrite( + &self, + cancellable: Option<&impl IsA>, + ) -> Result { + self.parent_open_readwrite(cancellable) + } + + fn create_readwrite( + &self, + flags: FileCreateFlags, + cancellable: Option<&impl IsA>, + ) -> Result { + self.parent_create_readwrite(flags, cancellable) + } + + fn replace_readwrite( + &self, + etag: Option<&str>, + make_backup: bool, + flags: FileCreateFlags, + cancellable: Option<&impl IsA>, + ) -> Result { + self.parent_replace_readwrite(etag, make_backup, flags, cancellable) + } + + fn start_mountable( + &self, + flags: DriveStartFlags, + mount_operation: Option<&MountOperation>, + cancellable: Option<&impl IsA>, + callback: Option

, + ) { + self.parent_start_mountable(flags, mount_operation, cancellable, callback) + } + + fn start_mountable_finish(&self, res: &AsyncResult) -> Result<(), Error> { + self.parent_start_mountable_finish(res) + } + + fn stop_mountable( + &self, + flags: MountUnmountFlags, + mount_operation: Option<&MountOperation>, + cancellable: Option<&impl IsA>, + callback: Option

, + ) { + self.parent_stop_mountable(flags, mount_operation, cancellable, callback) + } + + fn stop_mountable_finish(&self, res: &AsyncResult) -> Result<(), Error> { + self.parent_stop_mountable_finish(res) + } + + fn unmount_mountable_with_operation( + &self, + flags: MountUnmountFlags, + mount_operation: Option<&MountOperation>, + cancellable: Option<&impl IsA>, + callback: Option

, + ) { + self.parent_unmount_mountable_with_operation(flags, mount_operation, cancellable, callback) + } + + fn unmount_mountable_with_operation_finish(&self, res: &AsyncResult) -> Result<(), Error> { + self.parent_unmount_mountable_with_operation_finish(res) + } + + fn eject_mountable_with_operation( + &self, + flags: MountUnmountFlags, + mount_operation: Option<&MountOperation>, + cancellable: Option<&impl IsA>, + callback: Option

, + ) { + self.parent_eject_mountable_with_operation(flags, mount_operation, cancellable, callback) + } + + fn eject_mountable_with_operation_finish(&self, res: &AsyncResult) -> Result<(), Error> { + self.parent_eject_mountable_with_operation_finish(res) + } + + fn poll_mountable( + &self, + cancellable: Option<&impl IsA>, + callback: Option

, + ) { + self.parent_poll_mountable(cancellable, callback) + } + + fn poll_mountable_finish(&self, res: &AsyncResult) -> Result<(), Error> { + self.parent_poll_mountable_finish(res) + } + + fn measure_disk_usage( + &self, + flags: FileMeasureFlags, + cancellable: Option<&impl IsA>, + progress_callback: Option

, + ) -> Result<(u64, u64, u64), Error> { + self.parent_measure_disk_usage(flags, cancellable, progress_callback) + } + + fn query_exists(&self, cancellable: Option<&impl IsA>) -> bool { + self.parent_query_exists(cancellable) + } +} + +// Support parent implementation of virtual functions defined in `gio::ffi::GFileIface` except pairs `xxx_async/xxx_finish` for which GIO provides a default implementation. +pub trait FileImplExt: FileImpl { + fn parent_dup(&self) -> File { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + let func = (*parent_iface) + .dup + .expect("no parent \"dup\" implementation"); + let ret = func(self.obj().unsafe_cast_ref::().to_glib_none().0); + from_glib_full(ret) + } + } + + fn parent_hash(&self) -> u32 { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + let func = (*parent_iface) + .hash + .expect("no parent \"hash\" implementation"); + let ret = func(self.obj().unsafe_cast_ref::().to_glib_none().0); + ret + } + } + + fn parent_equal(&self, file2: &File) -> bool { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + let func = (*parent_iface) + .equal + .expect("no parent \"equal\" implementation"); + let ret = func( + self.obj().unsafe_cast_ref::().to_glib_none().0, + file2.to_glib_none().0, + ); + from_glib(ret) + } + } + + fn parent_is_native(&self) -> bool { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + let func = (*parent_iface) + .is_native + .expect("no parent \"is_native\" implementation"); + let ret = func(self.obj().unsafe_cast_ref::().to_glib_none().0); + from_glib(ret) + } + } + + fn parent_has_uri_scheme(&self, uri_scheme: &str) -> bool { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + let func = (*parent_iface) + .has_uri_scheme + .expect("no parent \"has_uri_scheme\" implementation"); + let ret = func( + self.obj().unsafe_cast_ref::().to_glib_none().0, + uri_scheme.to_glib_none().0, + ); + from_glib(ret) + } + } + + fn parent_uri_scheme(&self) -> Option { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + let func = (*parent_iface) + .get_uri_scheme + .expect("no parent \"get_uri_scheme\" implementation"); + let ret = func(self.obj().unsafe_cast_ref::().to_glib_none().0); + from_glib_full(ret) + } + } + + fn parent_basename(&self) -> Option { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + let func = (*parent_iface) + .get_basename + .expect("no parent \"get_basename\" implementation"); + let ret = func(self.obj().unsafe_cast_ref::().to_glib_none().0); + from_glib_full(ret) + } + } + + fn parent_path(&self) -> Option { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + let func = (*parent_iface) + .get_path + .expect("no parent \"get_path\" implementation"); + let ret = func(self.obj().unsafe_cast_ref::().to_glib_none().0); + from_glib_full(ret) + } + } + + fn parent_uri(&self) -> String { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + let func = (*parent_iface) + .get_uri + .expect("no parent \"get_uri\" implementation"); + let ret = func(self.obj().unsafe_cast_ref::().to_glib_none().0); + from_glib_full(ret) + } + } + + fn parent_parse_name(&self) -> String { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + let func = (*parent_iface) + .get_parse_name + .expect("no parent \"get_parse_name\" implementation"); + let ret = func(self.obj().unsafe_cast_ref::().to_glib_none().0); + from_glib_full(ret) + } + } + + fn parent_parent(&self) -> Option { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + let func = (*parent_iface) + .get_parent + .expect("no parent \"get_parent\" implementation"); + let ret = func(self.obj().unsafe_cast_ref::().to_glib_none().0); + from_glib_full(ret) + } + } + + fn parent_has_prefix(&self, prefix: &File) -> bool { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + let func = (*parent_iface) + .prefix_matches + .expect("no parent \"prefix_matches\" implementation"); + let ret = func( + prefix.to_glib_none().0, + self.obj().unsafe_cast_ref::().to_glib_none().0, + ); + from_glib(ret) + } + } + + fn parent_relative_path(&self, descendant: &File) -> Option { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + let func = (*parent_iface) + .get_relative_path + .expect("no parent \"get_relative_path\" implementation"); + let ret = func( + self.obj().unsafe_cast_ref::().to_glib_none().0, + descendant.to_glib_none().0, + ); + from_glib_full(ret) + } + } + + fn parent_resolve_relative_path(&self, relative_path: impl AsRef) -> File { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + let func = (*parent_iface) + .resolve_relative_path + .expect("no parent \"resolve_relative_path\" implementation"); + let ret = func( + self.obj().unsafe_cast_ref::().to_glib_none().0, + relative_path.as_ref().to_glib_none().0, + ); + from_glib_full(ret) + } + } + + fn parent_child_for_display_name(&self, display_name: &str) -> Result { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + let func = (*parent_iface) + .get_child_for_display_name + .expect("no parent \"get_child_for_display_name\" implementation"); + let mut error = std::ptr::null_mut(); + let ret = func( + self.obj().unsafe_cast_ref::().to_glib_none().0, + display_name.to_glib_none().0, + &mut error, + ); + if error.is_null() { + Ok(from_glib_full(ret)) + } else { + Err(from_glib_full(error)) + } + } + } + + fn parent_enumerate_children( + &self, + attributes: &str, + flags: FileQueryInfoFlags, + cancellable: Option<&impl IsA>, + ) -> Result { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + if let Some(func) = (*parent_iface).enumerate_children { + let mut error = std::ptr::null_mut(); + let ret = func( + self.obj().unsafe_cast_ref::().to_glib_none().0, + attributes.to_glib_none().0, + flags.into_glib(), + cancellable.map(|p| p.as_ref()).to_glib_none().0, + &mut error, + ); + if error.is_null() { + Ok(from_glib_full(ret)) + } else { + Err(from_glib_full(error)) + } + } else { + Err(Error::new::( + IOErrorEnum::NotSupported, + "Operation not supported", + )) + } + } + } + + fn parent_query_info( + &self, + attributes: &str, + flags: FileQueryInfoFlags, + cancellable: Option<&impl IsA>, + ) -> Result { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + if let Some(func) = (*parent_iface).query_info { + let mut error = std::ptr::null_mut(); + let ret = func( + self.obj().unsafe_cast_ref::().to_glib_none().0, + attributes.to_glib_none().0, + flags.into_glib(), + cancellable.map(|p| p.as_ref()).to_glib_none().0, + &mut error, + ); + if error.is_null() { + Ok(from_glib_full(ret)) + } else { + Err(from_glib_full(error)) + } + } else { + Err(Error::new::( + IOErrorEnum::NotSupported, + "Operation not supported", + )) + } + } + } + + fn parent_query_filesystem_info( + &self, + attributes: &str, + cancellable: Option<&impl IsA>, + ) -> Result { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + if let Some(func) = (*parent_iface).query_filesystem_info { + let mut error = std::ptr::null_mut(); + let ret = func( + self.obj().unsafe_cast_ref::().to_glib_none().0, + attributes.to_glib_none().0, + cancellable.map(|p| p.as_ref()).to_glib_none().0, + &mut error, + ); + if error.is_null() { + Ok(from_glib_full(ret)) + } else { + Err(from_glib_full(error)) + } + } else { + Err(Error::new::( + IOErrorEnum::NotSupported, + "Operation not supported", + )) + } + } + } + + fn parent_find_enclosing_mount( + &self, + cancellable: Option<&impl IsA>, + ) -> Result { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + if let Some(func) = (*parent_iface).find_enclosing_mount { + let mut error = std::ptr::null_mut(); + let ret = func( + self.obj().unsafe_cast_ref::().to_glib_none().0, + cancellable.map(|p| p.as_ref()).to_glib_none().0, + &mut error, + ); + if error.is_null() { + Ok(from_glib_full(ret)) + } else { + Err(from_glib_full(error)) + } + } else { + Err(Error::new::( + IOErrorEnum::NotFound, + "Containing mount does not exist", + )) + } + } + } + + fn parent_set_display_name( + &self, + display_name: &str, + cancellable: Option<&impl IsA>, + ) -> Result { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + let func = (*parent_iface) + .set_display_name + .expect("no parent \"set_display_name\" implementation"); + let mut error = std::ptr::null_mut(); + let ret = func( + self.obj().unsafe_cast_ref::().to_glib_none().0, + display_name.to_glib_none().0, + cancellable.map(|p| p.as_ref()).to_glib_none().0, + &mut error, + ); + if error.is_null() { + Ok(from_glib_full(ret)) + } else { + Err(from_glib_full(error)) + } + } + } + + fn parent_query_settable_attributes( + &self, + cancellable: Option<&impl IsA>, + ) -> Result { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + if let Some(func) = (*parent_iface).query_settable_attributes { + let mut error = std::ptr::null_mut(); + let ret = func( + self.obj().unsafe_cast_ref::().to_glib_none().0, + cancellable.map(|p| p.as_ref()).to_glib_none().0, + &mut error, + ); + if error.is_null() { + Ok(from_glib_full(ret)) + } else { + Err(from_glib_full(error)) + } + } else { + Ok(FileAttributeInfoList::new()) + } + } + } + + fn parent_query_writable_namespaces( + &self, + cancellable: Option<&impl IsA>, + ) -> Result { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + if let Some(func) = (*parent_iface).query_writable_namespaces { + let mut error = std::ptr::null_mut(); + let ret = func( + self.obj().unsafe_cast_ref::().to_glib_none().0, + cancellable.map(|p| p.as_ref()).to_glib_none().0, + &mut error, + ); + if error.is_null() { + Ok(from_glib_full(ret)) + } else { + Err(from_glib_full(error)) + } + } else { + Ok(FileAttributeInfoList::new()) + } + } + } + + fn parent_set_attribute<'a>( + &self, + attribute: &str, + value: impl Into>, + flags: FileQueryInfoFlags, + cancellable: Option<&impl IsA>, + ) -> Result<(), Error> { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + if let Some(func) = (*parent_iface).set_attribute { + let mut error = std::ptr::null_mut(); + let value: FileAttributeValue<'a> = value.into(); + let is_ok = func( + self.obj().unsafe_cast_ref::().to_glib_none().0, + attribute.to_glib_none().0, + value.type_().into_glib(), + value.as_ptr(), + flags.into_glib(), + cancellable.map(|p| p.as_ref()).to_glib_none().0, + &mut error, + ); + debug_assert_eq!(is_ok == glib::ffi::GFALSE, !error.is_null()); + if error.is_null() { + Ok(()) + } else { + Err(from_glib_full(error)) + } + } else { + Err(Error::new::( + IOErrorEnum::NotSupported, + "Operation not supported", + )) + } + } + } + + fn parent_set_attributes_from_info( + &self, + info: &FileInfo, + flags: FileQueryInfoFlags, + cancellable: Option<&impl IsA>, + ) -> Result<(), Error> { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + let func = (*parent_iface) + .set_attributes_from_info + .expect("no parent \"set_attributes_from_info\" implementation"); + let mut error = std::ptr::null_mut(); + let is_ok = func( + self.obj().unsafe_cast_ref::().to_glib_none().0, + info.to_glib_none().0, + flags.into_glib(), + cancellable.map(|p| p.as_ref()).to_glib_none().0, + &mut error, + ); + debug_assert_eq!(is_ok == glib::ffi::GFALSE, !error.is_null()); + if error.is_null() { + Ok(()) + } else { + Err(from_glib_full(error)) + } + } + } + + fn parent_read_fn( + &self, + cancellable: Option<&impl IsA>, + ) -> Result { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + if let Some(func) = (*parent_iface).read_fn { + let mut error = std::ptr::null_mut(); + let ret = func( + self.obj().unsafe_cast_ref::().to_glib_none().0, + cancellable.map(|p| p.as_ref()).to_glib_none().0, + &mut error, + ); + if error.is_null() { + Ok(from_glib_full(ret)) + } else { + Err(from_glib_full(error)) + } + } else { + Err(Error::new::( + IOErrorEnum::NotSupported, + "Operation not supported", + )) + } + } + } + + fn parent_append_to( + &self, + flags: FileCreateFlags, + cancellable: Option<&impl IsA>, + ) -> Result { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + if let Some(func) = (*parent_iface).append_to { + let mut error = std::ptr::null_mut(); + let ret = func( + self.obj().unsafe_cast_ref::().to_glib_none().0, + flags.into_glib(), + cancellable.map(|p| p.as_ref()).to_glib_none().0, + &mut error, + ); + if error.is_null() { + Ok(from_glib_full(ret)) + } else { + Err(from_glib_full(error)) + } + } else { + Err(Error::new::( + IOErrorEnum::NotSupported, + "Operation not supported", + )) + } + } + } + + fn parent_create( + &self, + flags: FileCreateFlags, + cancellable: Option<&impl IsA>, + ) -> Result { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + if let Some(func) = (*parent_iface).create { + let mut error = std::ptr::null_mut(); + let ret = func( + self.obj().unsafe_cast_ref::().to_glib_none().0, + flags.into_glib(), + cancellable.map(|p| p.as_ref()).to_glib_none().0, + &mut error, + ); + if error.is_null() { + Ok(from_glib_full(ret)) + } else { + Err(from_glib_full(error)) + } + } else { + Err(Error::new::( + IOErrorEnum::NotSupported, + "Operation not supported", + )) + } + } + } + + fn parent_replace( + &self, + etag: Option<&str>, + make_backup: bool, + flags: FileCreateFlags, + cancellable: Option<&impl IsA>, + ) -> Result { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + if let Some(func) = (*parent_iface).replace { + let mut error = std::ptr::null_mut(); + let ret = func( + self.obj().unsafe_cast_ref::().to_glib_none().0, + etag.to_glib_none().0, + make_backup.into_glib(), + flags.into_glib(), + cancellable.map(|p| p.as_ref()).to_glib_none().0, + &mut error, + ); + if error.is_null() { + Ok(from_glib_full(ret)) + } else { + Err(from_glib_full(error)) + } + } else { + Err(Error::new::( + IOErrorEnum::NotSupported, + "Operation not supported", + )) + } + } + } + + fn parent_delete(&self, cancellable: Option<&impl IsA>) -> Result<(), Error> { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + if let Some(func) = (*parent_iface).delete_file { + let mut error = std::ptr::null_mut(); + let is_ok = func( + self.obj().unsafe_cast_ref::().to_glib_none().0, + cancellable.map(|p| p.as_ref()).to_glib_none().0, + &mut error, + ); + debug_assert_eq!(is_ok == glib::ffi::GFALSE, !error.is_null()); + if error.is_null() { + Ok(()) + } else { + Err(from_glib_full(error)) + } + } else { + Err(Error::new::( + IOErrorEnum::NotSupported, + "Operation not supported", + )) + } + } + } + + fn parent_trash(&self, cancellable: Option<&impl IsA>) -> Result<(), Error> { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + if let Some(func) = (*parent_iface).trash { + let mut error = std::ptr::null_mut(); + let is_ok = func( + self.obj().unsafe_cast_ref::().to_glib_none().0, + cancellable.map(|p| p.as_ref()).to_glib_none().0, + &mut error, + ); + debug_assert_eq!(is_ok == glib::ffi::GFALSE, !error.is_null()); + if error.is_null() { + Ok(()) + } else { + Err(from_glib_full(error)) + } + } else { + Err(Error::new::( + IOErrorEnum::NotSupported, + "Operation not supported", + )) + } + } + } + + fn parent_make_directory( + &self, + cancellable: Option<&impl IsA>, + ) -> Result<(), Error> { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + if let Some(func) = (*parent_iface).make_directory { + let mut error = std::ptr::null_mut(); + let is_ok = func( + self.obj().unsafe_cast_ref::().to_glib_none().0, + cancellable.map(|p| p.as_ref()).to_glib_none().0, + &mut error, + ); + debug_assert_eq!(is_ok == glib::ffi::GFALSE, !error.is_null()); + if error.is_null() { + Ok(()) + } else { + Err(from_glib_full(error)) + } + } else { + Err(Error::new::( + IOErrorEnum::NotSupported, + "Operation not supported", + )) + } + } + } + + fn parent_make_symbolic_link( + &self, + symlink_value: impl AsRef, + cancellable: Option<&impl IsA>, + ) -> Result<(), Error> { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + if let Some(func) = (*parent_iface).make_symbolic_link { + let mut error = std::ptr::null_mut(); + let is_ok = func( + self.obj().unsafe_cast_ref::().to_glib_none().0, + symlink_value.as_ref().to_glib_none().0, + cancellable.map(|p| p.as_ref()).to_glib_none().0, + &mut error, + ); + debug_assert_eq!(is_ok == glib::ffi::GFALSE, !error.is_null()); + if error.is_null() { + Ok(()) + } else { + Err(from_glib_full(error)) + } + } else { + Err(Error::new::( + IOErrorEnum::NotSupported, + "Operation not supported", + )) + } + } + } + + fn parent_copy( + source: &File, + destination: &File, + flags: FileCopyFlags, + cancellable: Option<&impl IsA>, + progress_callback: Option<&mut dyn (FnMut(i64, i64))>, + ) -> Result<(), Error> { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + if let Some(func) = (*parent_iface).copy { + let (progress_callback, progress_callback_data) = + progress_callback.map_or((None, std::ptr::null_mut()), |progress_callback| { + let mut super_callback = progress_callback; + unsafe extern "C" fn progress_callback_func( + current_num_bytes: i64, + total_num_bytes: i64, + data: glib::ffi::gpointer, + ) { + let callback = &mut *(data as *mut P); + callback(current_num_bytes, total_num_bytes) + } + let callback = progress_callback_func::<&mut dyn (FnMut(i64, i64))>; + + (Some(callback as _), &mut super_callback as *mut _ as *mut _) + }); + + let mut error = std::ptr::null_mut(); + let is_ok = func( + source.to_glib_none().0, + destination.to_glib_none().0, + flags.into_glib(), + cancellable.map(|p| p.as_ref()).to_glib_none().0, + progress_callback, + progress_callback_data, + &mut error, + ); + debug_assert_eq!(is_ok == glib::ffi::GFALSE, !error.is_null()); + if error.is_null() { + Ok(()) + } else { + Err(from_glib_full(error)) + } + } else { + // give a chance to g_file_copy to call file_copy_fallback + Err(Error::new::( + IOErrorEnum::NotSupported, + "Operation not supported", + )) + } + } + } + + fn parent_move( + source: &File, + destination: &File, + flags: FileCopyFlags, + cancellable: Option<&impl IsA>, + progress_callback: Option<&mut dyn (FnMut(i64, i64))>, + ) -> Result<(), Error> { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + if let Some(func) = (*parent_iface).move_ { + let (progress_callback, progress_callback_data) = + progress_callback.map_or((None, std::ptr::null_mut()), |progress_callback| { + let mut super_callback = progress_callback; + unsafe extern "C" fn progress_callback_func( + current_num_bytes: i64, + total_num_bytes: i64, + data: glib::ffi::gpointer, + ) { + let callback = &mut *(data as *mut P); + callback(current_num_bytes, total_num_bytes) + } + let callback = progress_callback_func::<&mut dyn (FnMut(i64, i64))>; + + (Some(callback as _), &mut super_callback as *mut _ as *mut _) + }); + + let mut error = std::ptr::null_mut(); + let is_ok = func( + source.to_glib_none().0, + destination.to_glib_none().0, + flags.into_glib(), + cancellable.map(|p| p.as_ref()).to_glib_none().0, + progress_callback, + progress_callback_data, + &mut error, + ); + debug_assert_eq!(is_ok == glib::ffi::GFALSE, !error.is_null()); + if error.is_null() { + Ok(()) + } else { + Err(from_glib_full(error)) + } + } else { + // give a chance to g_file_move to call g_file_copy + Err(Error::new::( + IOErrorEnum::NotSupported, + "Operation not supported", + )) + } + } + } + + fn parent_mount_mountable( + &self, + flags: MountMountFlags, + mount_operation: Option<&MountOperation>, + cancellable: Option<&impl IsA>, + callback: Option

, + ) { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + let (callback, user_data) = callback.map_or((None, std::ptr::null_mut()), |callback| { + let super_callback = Box_::new(thread_guard::ThreadGuard::new(callback)); + + unsafe extern "C" fn callback_trampoline< + P: FnOnce(&Object, &AsyncResult) + 'static, + >( + source_object: *mut glib::gobject_ffi::GObject, + res: *mut ffi::GAsyncResult, + data: glib::ffi::gpointer, + ) { + let source_object: &Object = &from_glib_borrow(source_object); + let res: &AsyncResult = &from_glib_borrow(res); + let callback: Box_> = + Box_::from_raw(data as *mut _); + let callback: P = callback.into_inner(); + callback(source_object, res); + } + let callback = callback_trampoline::

; + + ( + Some(callback as _), + Box_::into_raw(super_callback) as *mut _, + ) + }); + + if let Some(func) = (*parent_iface).mount_mountable { + func( + self.obj().unsafe_cast_ref::().to_glib_none().0, + flags.into_glib(), + mount_operation.to_glib_none().0, + cancellable.map(|p| p.as_ref()).to_glib_none().0, + callback, + user_data, + ); + } else { + ffi::g_task_report_new_error( + self.obj().unsafe_cast_ref::().to_glib_none().0, + callback, + user_data, + ffi::g_file_mount_mountable as *mut _, + IOErrorEnum::domain().into_glib(), + IOErrorEnum::NotSupported.into_glib(), + "Operation not supported".to_glib_full(), + ); + } + } + } + + fn parent_mount_mountable_finish(&self, res: &AsyncResult) -> Result { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + if let Some(func) = (*parent_iface).mount_mountable_finish { + let mut error = std::ptr::null_mut(); + let ret = func( + self.obj().unsafe_cast_ref::().to_glib_none().0, + res.to_glib_none().0, + &mut error, + ); + if error.is_null() { + Ok(from_glib_full(ret)) + } else { + Err(from_glib_full(error)) + } + } else if let Some(task) = res.downcast_ref::>() { + // get the `Task` result as a `File` or as an error + task.to_owned().propagate() + } else { + // no parent implementation and don't know how to deal with the result so let's panic + panic!("no parent \"mount_mountable_finish\" implementation") + } + } + } + + fn parent_unmount_mountable( + &self, + flags: MountUnmountFlags, + cancellable: Option<&impl IsA>, + callback: Option

, + ) { + let (callback, user_data) = callback.map_or((None, std::ptr::null_mut()), |callback| { + let super_callback = Box_::new(thread_guard::ThreadGuard::new(callback)); + + unsafe extern "C" fn callback_trampoline( + source_object: *mut glib::gobject_ffi::GObject, + res: *mut ffi::GAsyncResult, + data: glib::ffi::gpointer, + ) { + let source_object: &Object = &from_glib_borrow(source_object); + let res: &AsyncResult = &from_glib_borrow(res); + let callback: Box_> = Box_::from_raw(data as *mut _); + let callback: P = callback.into_inner(); + callback(source_object, res); + } + let callback = callback_trampoline::

; + + ( + Some(callback as _), + Box_::into_raw(super_callback) as *mut _, + ) + }); + + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + if let Some(func) = (*parent_iface).unmount_mountable { + func( + self.obj().unsafe_cast_ref::().to_glib_none().0, + flags.into_glib(), + cancellable.map(|p| p.as_ref()).to_glib_none().0, + callback, + user_data, + ); + } else { + ffi::g_task_report_new_error( + self.obj().unsafe_cast_ref::().to_glib_none().0, + callback, + user_data, + ffi::g_file_unmount_mountable_with_operation as *mut _, + IOErrorEnum::domain().into_glib(), + IOErrorEnum::NotSupported.into_glib(), + "Operation not supported".to_glib_full(), + ); + } + } + } + + fn parent_unmount_mountable_finish(&self, res: &AsyncResult) -> Result<(), Error> { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + if let Some(func) = (*parent_iface).unmount_mountable_finish { + let mut error = std::ptr::null_mut(); + let is_ok = func( + self.obj().unsafe_cast_ref::().to_glib_none().0, + res.to_glib_none().0, + &mut error, + ); + debug_assert_eq!(is_ok == glib::ffi::GFALSE, !error.is_null()); + if error.is_null() { + Ok(()) + } else { + Err(from_glib_full(error)) + } + } else if let Some(task) = res.downcast_ref::>() { + // get the `Task` result as a boolean or as an error + task.to_owned().propagate().map(|_| ()) + } else { + // no parent implementation and don't know how to deal with the result so let's panic + panic!("no parent \"unmount_mountable_finish\" implementation") + } + } + } + + fn parent_eject_mountable( + &self, + flags: MountUnmountFlags, + cancellable: Option<&impl IsA>, + callback: Option

, + ) { + let (callback, user_data) = callback.map_or((None, std::ptr::null_mut()), |callback| { + let super_callback = Box_::new(thread_guard::ThreadGuard::new(callback)); + + unsafe extern "C" fn callback_trampoline( + source_object: *mut glib::gobject_ffi::GObject, + res: *mut ffi::GAsyncResult, + data: glib::ffi::gpointer, + ) { + let source_object: &Object = &from_glib_borrow(source_object); + let res: &AsyncResult = &from_glib_borrow(res); + let callback: Box_> = Box_::from_raw(data as *mut _); + let callback: P = callback.into_inner(); + callback(source_object, res); + } + let callback = callback_trampoline::

; + + ( + Some(callback as _), + Box_::into_raw(super_callback) as *mut _, + ) + }); + + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + if let Some(func) = (*parent_iface).eject_mountable { + func( + self.obj().unsafe_cast_ref::().to_glib_none().0, + flags.into_glib(), + cancellable.map(|p| p.as_ref()).to_glib_none().0, + callback, + user_data, + ); + } else { + ffi::g_task_report_new_error( + self.obj().unsafe_cast_ref::().to_glib_none().0, + callback, + user_data, + ffi::g_file_eject_mountable_with_operation as *mut _, + IOErrorEnum::domain().into_glib(), + IOErrorEnum::NotSupported.into_glib(), + "Operation not supported".to_glib_full(), + ); + } + } + } + + fn parent_eject_mountable_finish(&self, res: &AsyncResult) -> Result<(), Error> { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + if let Some(func) = (*parent_iface).eject_mountable_finish { + let mut error = std::ptr::null_mut(); + let is_ok = func( + self.obj().unsafe_cast_ref::().to_glib_none().0, + res.to_glib_none().0, + &mut error, + ); + debug_assert_eq!(is_ok == glib::ffi::GFALSE, !error.is_null()); + if error.is_null() { + Ok(()) + } else { + Err(from_glib_full(error)) + } + } else if let Some(task) = res.downcast_ref::>() { + // get the `Task` result as a boolean or as an error + task.to_owned().propagate().map(|_| ()) + } else { + // no parent implementation and don't know how to deal with the result so let's panic + panic!("no parent \"eject_mountable_finish\" implementation") + } + } + } + + fn parent_mount_enclosing_volume( + &self, + flags: MountMountFlags, + mount_operation: Option<&MountOperation>, + cancellable: Option<&impl IsA>, + callback: Option

, + ) { + let (callback, user_data) = callback.map_or((None, std::ptr::null_mut()), |callback| { + let super_callback = Box_::new(thread_guard::ThreadGuard::new(callback)); + + unsafe extern "C" fn callback_trampoline( + source_object: *mut glib::gobject_ffi::GObject, + res: *mut ffi::GAsyncResult, + data: glib::ffi::gpointer, + ) { + let source_object: &Object = &from_glib_borrow(source_object); + let res: &AsyncResult = &from_glib_borrow(res); + let callback: Box_> = Box_::from_raw(data as *mut _); + let callback: P = callback.into_inner(); + callback(source_object, res); + } + let callback = callback_trampoline::

; + + ( + Some(callback as _), + Box_::into_raw(super_callback) as *mut _, + ) + }); + + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + if let Some(func) = (*parent_iface).mount_enclosing_volume { + func( + self.obj().unsafe_cast_ref::().to_glib_none().0, + flags.into_glib(), + mount_operation.to_glib_none().0, + cancellable.map(|p| p.as_ref()).to_glib_none().0, + callback, + user_data, + ); + } else { + ffi::g_task_report_new_error( + self.obj().unsafe_cast_ref::().to_glib_none().0, + callback, + user_data, + ffi::g_file_mount_enclosing_volume as *mut _, + IOErrorEnum::domain().into_glib(), + IOErrorEnum::NotSupported.into_glib(), + "volume doesn’t implement mooooount".to_glib_full(), + ); + } + } + } + + fn parent_mount_enclosing_volume_finish(&self, res: &AsyncResult) -> Result<(), Error> { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + if let Some(func) = (*parent_iface).mount_enclosing_volume_finish { + let mut error = std::ptr::null_mut(); + let is_ok = func( + self.obj().unsafe_cast_ref::().to_glib_none().0, + res.to_glib_none().0, + &mut error, + ); + debug_assert_eq!(is_ok == glib::ffi::GFALSE, !error.is_null()); + if error.is_null() { + Ok(()) + } else { + Err(from_glib_full(error)) + } + } else if let Some(task) = res.downcast_ref::>() { + // get the `Task` result as a boolean or as an error + task.to_owned().propagate().map(|_| ()) + } else { + // no parent implementation and don't know how to deal with the result so let's panic + panic!("no parent \"mount_enclosing_volume_finish\" implementation") + } + } + } + + fn parent_monitor_dir( + &self, + flags: FileMonitorFlags, + cancellable: Option<&impl IsA>, + ) -> Result { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + if let Some(func) = (*parent_iface).monitor_dir { + let mut error = std::ptr::null_mut(); + let ret = func( + self.obj().unsafe_cast_ref::().to_glib_none().0, + flags.into_glib(), + cancellable.map(|p| p.as_ref()).to_glib_none().0, + &mut error, + ); + if error.is_null() { + Ok(from_glib_full(ret)) + } else { + Err(from_glib_full(error)) + } + } else { + Err(Error::new::( + IOErrorEnum::NotSupported, + "Operation not supported", + )) + } + } + } + + fn parent_monitor_file( + &self, + flags: FileMonitorFlags, + cancellable: Option<&impl IsA>, + ) -> Result { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + if let Some(func) = (*parent_iface).monitor_file { + let mut error = std::ptr::null_mut(); + let ret = func( + self.obj().unsafe_cast_ref::().to_glib_none().0, + flags.into_glib(), + cancellable.map(|p| p.as_ref()).to_glib_none().0, + &mut error, + ); + if error.is_null() { + Ok(from_glib_full(ret)) + } else { + Err(from_glib_full(error)) + } + } else { + // cannot call private _g_poll_file_monitor_new + panic!("no parent \"monitor_file\" implementation") + } + } + } + + fn parent_open_readwrite( + &self, + cancellable: Option<&impl IsA>, + ) -> Result { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + if let Some(func) = (*parent_iface).open_readwrite { + let mut error = std::ptr::null_mut(); + let ret = func( + self.obj().unsafe_cast_ref::().to_glib_none().0, + cancellable.map(|p| p.as_ref()).to_glib_none().0, + &mut error, + ); + if error.is_null() { + Ok(from_glib_full(ret)) + } else { + Err(from_glib_full(error)) + } + } else { + Err(Error::new::( + IOErrorEnum::NotSupported, + "Operation not supported", + )) + } + } + } + + fn parent_create_readwrite( + &self, + flags: FileCreateFlags, + cancellable: Option<&impl IsA>, + ) -> Result { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + if let Some(func) = (*parent_iface).create_readwrite { + let mut error = std::ptr::null_mut(); + let ret = func( + self.obj().unsafe_cast_ref::().to_glib_none().0, + flags.into_glib(), + cancellable.map(|p| p.as_ref()).to_glib_none().0, + &mut error, + ); + if error.is_null() { + Ok(from_glib_full(ret)) + } else { + Err(from_glib_full(error)) + } + } else { + Err(Error::new::( + IOErrorEnum::NotSupported, + "Operation not supported", + )) + } + } + } + + fn parent_replace_readwrite( + &self, + etag: Option<&str>, + make_backup: bool, + flags: FileCreateFlags, + cancellable: Option<&impl IsA>, + ) -> Result { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + if let Some(func) = (*parent_iface).replace_readwrite { + let mut error = std::ptr::null_mut(); + let ret = func( + self.obj().unsafe_cast_ref::().to_glib_none().0, + etag.to_glib_none().0, + make_backup.into_glib(), + flags.into_glib(), + cancellable.map(|p| p.as_ref()).to_glib_none().0, + &mut error, + ); + if error.is_null() { + Ok(from_glib_full(ret)) + } else { + Err(from_glib_full(error)) + } + } else { + Err(Error::new::( + IOErrorEnum::NotSupported, + "Operation not supported", + )) + } + } + } + + fn parent_start_mountable( + &self, + flags: DriveStartFlags, + mount_operation: Option<&MountOperation>, + cancellable: Option<&impl IsA>, + callback: Option

, + ) { + let (callback, user_data) = callback.map_or((None, std::ptr::null_mut()), |callback| { + let super_callback = Box_::new(thread_guard::ThreadGuard::new(callback)); + + unsafe extern "C" fn callback_trampoline( + source_object: *mut glib::gobject_ffi::GObject, + res: *mut ffi::GAsyncResult, + data: glib::ffi::gpointer, + ) { + let source_object: &Object = &from_glib_borrow(source_object); + let res: &AsyncResult = &from_glib_borrow(res); + let callback: Box_> = Box_::from_raw(data as *mut _); + let callback: P = callback.into_inner(); + callback(source_object, res); + } + let callback = callback_trampoline::

; + + ( + Some(callback as _), + Box_::into_raw(super_callback) as *mut _, + ) + }); + + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + if let Some(func) = (*parent_iface).start_mountable { + func( + self.obj().unsafe_cast_ref::().to_glib_none().0, + flags.into_glib(), + mount_operation.to_glib_none().0, + cancellable.map(|p| p.as_ref()).to_glib_none().0, + callback, + user_data, + ); + } else { + ffi::g_task_report_new_error( + self.obj().unsafe_cast_ref::().to_glib_none().0, + callback, + user_data, + ffi::g_file_start_mountable as *mut _, + IOErrorEnum::domain().into_glib(), + IOErrorEnum::NotSupported.into_glib(), + "Operation not supported".to_glib_full(), + ); + } + } + } + + fn parent_start_mountable_finish(&self, res: &AsyncResult) -> Result<(), Error> { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + if let Some(func) = (*parent_iface).start_mountable_finish { + let mut error = std::ptr::null_mut(); + let is_ok = func( + self.obj().unsafe_cast_ref::().to_glib_none().0, + res.to_glib_none().0, + &mut error, + ); + debug_assert_eq!(is_ok == glib::ffi::GFALSE, !error.is_null()); + if error.is_null() { + Ok(()) + } else { + Err(from_glib_full(error)) + } + } else if let Some(task) = res.downcast_ref::>() { + // get the `Task` result as a boolean or as an error + task.to_owned().propagate().map(|_| ()) + } else { + // no parent implementation and don't know how to deal with the result so let's panic + panic!("no parent \"start_mountable_finish\" implementation") + } + } + } + + fn parent_stop_mountable( + &self, + flags: MountUnmountFlags, + mount_operation: Option<&MountOperation>, + cancellable: Option<&impl IsA>, + callback: Option

, + ) { + let (callback, user_data) = callback.map_or((None, std::ptr::null_mut()), |callback| { + let super_callback = Box_::new(thread_guard::ThreadGuard::new(callback)); + + unsafe extern "C" fn callback_trampoline( + source_object: *mut glib::gobject_ffi::GObject, + res: *mut ffi::GAsyncResult, + data: glib::ffi::gpointer, + ) { + let source_object: &Object = &from_glib_borrow(source_object); + let res: &AsyncResult = &from_glib_borrow(res); + let callback: Box_> = Box_::from_raw(data as *mut _); + let callback: P = callback.into_inner(); + callback(source_object, res); + } + let callback = callback_trampoline::

; + + ( + Some(callback as _), + Box_::into_raw(super_callback) as *mut _, + ) + }); + + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + if let Some(func) = (*parent_iface).stop_mountable { + func( + self.obj().unsafe_cast_ref::().to_glib_none().0, + flags.into_glib(), + mount_operation.to_glib_none().0, + cancellable.map(|p| p.as_ref()).to_glib_none().0, + callback, + user_data, + ); + } else { + ffi::g_task_report_new_error( + self.obj().unsafe_cast_ref::().to_glib_none().0, + callback, + user_data, + ffi::g_file_stop_mountable as *mut _, + IOErrorEnum::domain().into_glib(), + IOErrorEnum::NotSupported.into_glib(), + "Operation not supported".to_glib_full(), + ); + } + } + } + + fn parent_stop_mountable_finish(&self, res: &AsyncResult) -> Result<(), Error> { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + if let Some(func) = (*parent_iface).stop_mountable_finish { + let mut error = std::ptr::null_mut(); + let is_ok = func( + self.obj().unsafe_cast_ref::().to_glib_none().0, + res.to_glib_none().0, + &mut error, + ); + debug_assert_eq!(is_ok == glib::ffi::GFALSE, !error.is_null()); + if error.is_null() { + Ok(()) + } else { + Err(from_glib_full(error)) + } + } else if let Some(task) = res.downcast_ref::>() { + // get the `Task` result as a boolean or as an error + task.to_owned().propagate().map(|_| ()) + } else { + // no parent implementation and don't know how to deal with the result so let's panic + panic!("no parent \"stop_mountable_finish\" implementation") + } + } + } + + fn parent_unmount_mountable_with_operation( + &self, + flags: MountUnmountFlags, + mount_operation: Option<&MountOperation>, + cancellable: Option<&impl IsA>, + callback: Option

, + ) { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + if let Some(func) = (*parent_iface).unmount_mountable_with_operation { + let (callback, user_data) = + callback.map_or((None, std::ptr::null_mut()), |callback| { + let super_callback = Box_::new(thread_guard::ThreadGuard::new(callback)); + + unsafe extern "C" fn callback_trampoline< + P: FnOnce(&Object, &AsyncResult) + 'static, + >( + source_object: *mut glib::gobject_ffi::GObject, + res: *mut ffi::GAsyncResult, + data: glib::ffi::gpointer, + ) { + let source_object: &Object = &from_glib_borrow(source_object); + let res: &AsyncResult = &from_glib_borrow(res); + let callback: Box_> = + Box_::from_raw(data as *mut _); + let callback: P = callback.into_inner(); + callback(source_object, res); + } + let callback = callback_trampoline::

; + + ( + Some(callback as _), + Box_::into_raw(super_callback) as *mut _, + ) + }); + + func( + self.obj().unsafe_cast_ref::().to_glib_none().0, + flags.into_glib(), + mount_operation.to_glib_none().0, + cancellable.map(|p| p.as_ref()).to_glib_none().0, + callback, + user_data, + ) + } else { + self.unmount_mountable(flags, cancellable, callback); + } + } + } + + fn parent_unmount_mountable_with_operation_finish( + &self, + res: &AsyncResult, + ) -> Result<(), Error> { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + if let Some(func) = (*parent_iface).unmount_mountable_with_operation_finish { + let mut error = std::ptr::null_mut(); + let is_ok = func( + self.obj().unsafe_cast_ref::().to_glib_none().0, + res.to_glib_none().0, + &mut error, + ); + debug_assert_eq!(is_ok == glib::ffi::GFALSE, !error.is_null()); + if error.is_null() { + Ok(()) + } else { + Err(from_glib_full(error)) + } + } else { + self.unmount_mountable_finish(res) + } + } + } + + fn parent_eject_mountable_with_operation( + &self, + flags: MountUnmountFlags, + mount_operation: Option<&MountOperation>, + cancellable: Option<&impl IsA>, + callback: Option

, + ) { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + if let Some(func) = (*parent_iface).eject_mountable_with_operation { + let (callback, user_data) = + callback.map_or((None, std::ptr::null_mut()), |callback| { + let super_callback = Box_::new(thread_guard::ThreadGuard::new(callback)); + + unsafe extern "C" fn callback_trampoline< + P: FnOnce(&Object, &AsyncResult) + 'static, + >( + source_object: *mut glib::gobject_ffi::GObject, + res: *mut ffi::GAsyncResult, + data: glib::ffi::gpointer, + ) { + let source_object: &Object = &from_glib_borrow(source_object); + let res: &AsyncResult = &from_glib_borrow(res); + let callback: Box_> = + Box_::from_raw(data as *mut _); + let callback: P = callback.into_inner(); + callback(source_object, res); + } + let callback = callback_trampoline::

; + + ( + Some(callback as _), + Box_::into_raw(super_callback) as *mut _, + ) + }); + + func( + self.obj().unsafe_cast_ref::().to_glib_none().0, + flags.into_glib(), + mount_operation.to_glib_none().0, + cancellable.map(|p| p.as_ref()).to_glib_none().0, + callback, + user_data, + ); + } else { + self.eject_mountable(flags, cancellable, callback); + } + } + } + + fn parent_eject_mountable_with_operation_finish(&self, res: &AsyncResult) -> Result<(), Error> { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + if let Some(func) = (*parent_iface).eject_mountable_with_operation_finish { + let mut error = std::ptr::null_mut(); + let is_ok = func( + self.obj().unsafe_cast_ref::().to_glib_none().0, + res.to_glib_none().0, + &mut error, + ); + debug_assert_eq!(is_ok == glib::ffi::GFALSE, !error.is_null()); + if error.is_null() { + Ok(()) + } else { + Err(from_glib_full(error)) + } + } else { + self.eject_mountable_finish(res) + } + } + } + + fn parent_poll_mountable( + &self, + cancellable: Option<&impl IsA>, + callback: Option

, + ) { + let (callback, user_data) = callback.map_or((None, std::ptr::null_mut()), |callback| { + let super_callback = Box_::new(thread_guard::ThreadGuard::new(callback)); + + unsafe extern "C" fn callback_trampoline( + source_object: *mut glib::gobject_ffi::GObject, + res: *mut ffi::GAsyncResult, + data: glib::ffi::gpointer, + ) { + let source_object: &Object = &from_glib_borrow(source_object); + let res: &AsyncResult = &from_glib_borrow(res); + let callback: Box_> = Box_::from_raw(data as *mut _); + let callback: P = callback.into_inner(); + callback(source_object, res); + } + let callback = callback_trampoline::

; + + ( + Some(callback as _), + Box_::into_raw(super_callback) as *mut _, + ) + }); + + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + if let Some(func) = (*parent_iface).poll_mountable { + func( + self.obj().unsafe_cast_ref::().to_glib_none().0, + cancellable.map(|p| p.as_ref()).to_glib_none().0, + callback, + user_data, + ); + } else { + ffi::g_task_report_new_error( + self.obj().unsafe_cast_ref::().to_glib_none().0, + callback, + user_data, + ffi::g_file_poll_mountable as *mut _, + IOErrorEnum::domain().into_glib(), + IOErrorEnum::NotSupported.into_glib(), + "Operation not supported".to_glib_full(), + ); + } + } + } + + fn parent_poll_mountable_finish(&self, res: &AsyncResult) -> Result<(), Error> { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + if let Some(func) = (*parent_iface).poll_mountable_finish { + let mut error = std::ptr::null_mut(); + let is_ok = func( + self.obj().unsafe_cast_ref::().to_glib_none().0, + res.to_glib_none().0, + &mut error, + ); + debug_assert_eq!(is_ok == glib::ffi::GFALSE, !error.is_null()); + if error.is_null() { + Ok(()) + } else { + Err(from_glib_full(error)) + } + } else if let Some(task) = res.downcast_ref::>() { + // get the `Task` result as a boolean or as an error + task.to_owned().propagate().map(|_| ()) + } else { + // no parent implementation and don't know how to deal with the result so let's panic + panic!("no parent \"poll_mountable_finish\" implementation") + } + } + } + + fn parent_measure_disk_usage( + &self, + flags: FileMeasureFlags, + cancellable: Option<&impl IsA>, + progress_callback: Option

, + ) -> Result<(u64, u64, u64), Error> { + let (progress_callback, progress_data) = + progress_callback.map_or((None, std::ptr::null_mut()), |progress_callback| { + let mut super_callback: Option

= Some(progress_callback); + unsafe extern "C" fn progress_callback_func< + P: FnMut(bool, u64, u64, u64) + 'static, + >( + reporting: glib::ffi::gboolean, + current_size: u64, + num_dirs: u64, + num_files: u64, + data: glib::ffi::gpointer, + ) { + let callback = data as *mut Option

; + if let Some(ref mut callback) = *callback { + callback(from_glib(reporting), current_size, num_dirs, num_files) + } else { + panic!("cannot get closure...") + } + } + let callback = progress_callback_func::

; + + (Some(callback as _), &mut super_callback as *mut _ as *mut _) + }); + + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + let func = (*parent_iface) + .measure_disk_usage + .expect("no parent \"measure_disk_usage\" implementation"); + let mut disk_usage = 0u64; + let mut num_dirs = 0u64; + let mut num_files = 0u64; + let mut error = std::ptr::null_mut(); + let is_ok = func( + self.obj().unsafe_cast_ref::().to_glib_none().0, + flags.into_glib(), + cancellable.map(|p| p.as_ref()).to_glib_none().0, + progress_callback, + progress_data, + &mut disk_usage, + &mut num_dirs, + &mut num_files, + &mut error, + ); + debug_assert_eq!(is_ok == glib::ffi::GFALSE, !error.is_null()); + if error.is_null() { + Ok((disk_usage, num_dirs, num_files)) + } else { + Err(from_glib_full(error)) + } + } + } + + fn parent_query_exists(&self, cancellable: Option<&impl IsA>) -> bool { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GFileIface; + + if let Some(func) = (*parent_iface).query_exists { + let ret = func( + self.obj().unsafe_cast_ref::().to_glib_none().0, + cancellable.map(|p| p.as_ref()).to_glib_none().0, + ); + from_glib(ret) + } else { + let file_info = + self.query_info("standard::type", FileQueryInfoFlags::NONE, cancellable); + file_info.is_ok() + } + } + } +} + +impl FileImplExt for T {} + +// Implement virtual functions defined in `gio::ffi::GFileIface` except pairs `xxx_async/xxx_finish` for which GIO provides a default implementation. +unsafe impl IsImplementable for File { + fn interface_init(iface: &mut Interface) { + let iface = iface.as_mut(); + + iface.dup = Some(file_dup::); + iface.hash = Some(file_hash::); + iface.equal = Some(file_equal::); + iface.is_native = Some(file_is_native::); + iface.has_uri_scheme = Some(file_has_uri_scheme::); + iface.get_uri_scheme = Some(file_get_uri_scheme::); + iface.get_basename = Some(file_get_basename::); + iface.get_path = Some(file_get_path::); + iface.get_uri = Some(file_get_uri::); + iface.get_parse_name = Some(file_get_parse_name::); + iface.get_parent = Some(file_get_parent::); + iface.prefix_matches = Some(file_prefix_matches::); + iface.get_relative_path = Some(file_get_relative_path::); + iface.resolve_relative_path = Some(file_resolve_relative_path::); + iface.get_child_for_display_name = Some(file_get_child_for_display_name::); + iface.enumerate_children = Some(file_enumerate_children::); + iface.query_info = Some(file_query_info::); + iface.query_filesystem_info = Some(file_query_filesystem_info::); + iface.find_enclosing_mount = Some(file_find_enclosing_mount::); + iface.set_display_name = Some(file_set_display_name::); + iface.query_settable_attributes = Some(file_query_settable_attributes::); + iface.query_writable_namespaces = Some(file_query_writable_namespaces::); + iface.set_attribute = Some(file_set_attribute::); + iface.set_attributes_from_info = Some(file_set_attributes_from_info::); + iface.read_fn = Some(file_read_fn::); + iface.append_to = Some(file_append_to::); + iface.create = Some(file_create::); + iface.replace = Some(file_replace::); + iface.delete_file = Some(file_delete_file::); + iface.trash = Some(file_trash::); + iface.make_directory = Some(file_make_directory::); + iface.make_symbolic_link = Some(file_make_symbolic_link::); + iface.copy = Some(file_copy::); + iface.move_ = Some(file_move::); + iface.mount_mountable = Some(file_mount_mountable::); + iface.mount_mountable_finish = Some(file_mount_mountable_finish::); + iface.unmount_mountable = Some(file_unmount_mountable::); + iface.unmount_mountable_finish = Some(file_unmount_mountable_finish::); + iface.eject_mountable = Some(file_eject_mountable::); + iface.eject_mountable_finish = Some(file_eject_mountable_finish::); + iface.mount_enclosing_volume = Some(file_mount_enclosing_volume::); + iface.mount_enclosing_volume_finish = Some(file_mount_enclosing_volume_finish::); + iface.monitor_dir = Some(file_monitor_dir::); + iface.monitor_file = Some(file_monitor_file::); + iface.open_readwrite = Some(file_open_readwrite::); + iface.create_readwrite = Some(file_create_readwrite::); + iface.replace_readwrite = Some(file_replace_readwrite::); + iface.start_mountable = Some(file_start_mountable::); + iface.start_mountable_finish = Some(file_start_mountable_finish::); + iface.stop_mountable = Some(file_stop_mountable::); + iface.stop_mountable_finish = Some(file_stop_mountable_finish::); + iface.supports_thread_contexts = T::SUPPORT_THREAD_CONTEXT.into_glib(); + iface.unmount_mountable_with_operation = Some(file_unmount_mountable_with_operation::); + iface.unmount_mountable_with_operation_finish = + Some(file_unmount_mountable_with_operation_finish::); + iface.eject_mountable_with_operation = Some(file_eject_mountable_with_operation::); + iface.eject_mountable_with_operation_finish = + Some(file_eject_mountable_with_operation_finish::); + iface.poll_mountable = Some(file_poll_mountable::); + iface.poll_mountable_finish = Some(file_poll_mountable_finish::); + iface.measure_disk_usage = Some(file_measure_disk_usage::); + #[cfg(feature = "v2_84")] + { + iface.query_exists = Some(file_query_exists::); + } + // `GFile` already implements `xxx_async/xxx_finish` vfuncs and this should be ok. + // TODO: when needed, override the `GFile` implementation of the following vfuncs: + // iface.enumerate_children_async = Some(file_enumerate_children_async::); + // iface.enumerate_children_finish = Some(file_enumerate_children_finish::); + // iface.query_info_async = Some(file_query_info_async::); + // iface.query_info_finish = Some(file_query_info_finish::); + // iface.query_filesystem_info_async = Some(file_query_filesystem_info_async::); + // iface.query_filesystem_info_finish = Some(file_query_filesystem_info_finish::); + // iface.find_enclosing_mount_async = Some(file_find_enclosing_mount_asyncv); + // iface.find_enclosing_mount_finish = Some(file_find_enclosing_mount_finish::); + // iface.set_display_name_async = Some(file_set_display_name_async::); + // iface.set_display_name_finish = Some(file_set_display_name_finish::); + // iface._query_settable_attributes_async = Some(_file_query_settable_attributes_async::); + // iface._query_settable_attributes_finish = Some(_file_query_settable_attributes_finish::); + // iface._query_writable_namespaces_async = Some(_file_query_writable_namespaces_async::); + // iface._query_writable_namespaces_finish = Some(_file_query_writable_namespaces_finish::); + // iface.set_attributes_async = Some(file_set_attributes_async::); + // iface.set_attributes_finish = Some(file_set_attributes_finishv); + // iface.read_async = Some(file_read_async::); + // iface.read_finish = Some(file_read_finish::); + // iface.append_to_async = Some(file_append_to_async::); + // iface.append_to_finish = Some(file_append_to_finish::); + // iface.create_async = Some(file_create_async::); + // iface.create_finish = Some(file_create_finish::); + // iface.replace_async = Some(file_replace_async::); + // iface.replace_finish = Some(file_replace_finish::); + // iface.delete_file_async = Some(file_delete_file_async::); + // iface.delete_file_finish = Some(file_delete_file_finish::); + // iface.trash_async = Some(file_trash_async::); + // iface.trash_finish = Some(file_trash_finish::); + // iface.make_directory_async = Some(file_make_directory_async::); + // iface.make_directory_finish = Some(file_make_directory_finish::); + // iface.make_symbolic_link_async = Some(file_make_symbolic_link_async::); + // iface.make_symbolic_link_finish = Some(file_make_symbolic_link_finish::); + // iface.copy_async = Some(file_copy_async::); + // iface.copy_finish = Some(file_copy_finish::); + // iface.move_async = Some(file_move_async::); + // iface.move_finish = Some(file_move_finish::); + // iface.open_readwrite_async = Some(file_open_readwrite_async::); + // iface.open_readwrite_finish = Some(file_open_readwrite_finish::); + // iface.create_readwrite_async = Some(file_create_readwrite_async::); + // iface.create_readwrite_finish = Some(file_create_readwrite_finish::); + // iface.replace_readwrite_async = Some(file_replace_readwrite_async::); + // iface.replace_readwrite_finish = Some(file_replace_readwrite_finish::); + // iface.measure_disk_usage_async = Some(file_measure_disk_usage_async::); + // iface.measure_disk_usage_finish = Some(file_measure_disk_usage_finish::); + } +} + +unsafe extern "C" fn file_dup(file: *mut ffi::GFile) -> *mut ffi::GFile { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + + imp.dup().to_glib_full() +} + +unsafe extern "C" fn file_hash(file: *mut ffi::GFile) -> c_uint { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + + imp.hash() +} + +unsafe extern "C" fn file_equal( + file1: *mut ffi::GFile, + file2: *mut ffi::GFile, +) -> glib::ffi::gboolean { + let instance = &*(file1 as *mut T::Instance); + let imp = instance.imp(); + + imp.equal(&from_glib_borrow(file2)).into_glib() +} + +unsafe extern "C" fn file_is_native(file: *mut ffi::GFile) -> glib::ffi::gboolean { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + + imp.is_native().into_glib() +} + +unsafe extern "C" fn file_has_uri_scheme( + file: *mut ffi::GFile, + uri_scheme: *const c_char, +) -> glib::ffi::gboolean { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + + imp.has_uri_scheme(&GString::from_glib_borrow(uri_scheme)) + .into_glib() +} + +unsafe extern "C" fn file_get_uri_scheme(file: *mut ffi::GFile) -> *mut c_char { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + + let res = imp.uri_scheme(); + if let Some(uri_scheme) = res { + uri_scheme.to_glib_full() + } else { + std::ptr::null_mut() + } +} + +unsafe extern "C" fn file_get_basename(file: *mut ffi::GFile) -> *mut c_char { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + + let res = imp.basename(); + if let Some(basename) = res { + basename.to_glib_full() + } else { + std::ptr::null_mut() + } +} + +unsafe extern "C" fn file_get_path(file: *mut ffi::GFile) -> *mut c_char { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + + let res = imp.path(); + if let Some(path) = res { + path.to_glib_full() + } else { + std::ptr::null_mut() + } +} + +unsafe extern "C" fn file_get_uri(file: *mut ffi::GFile) -> *mut c_char { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + + let uri = imp.uri(); + uri.to_glib_full() +} + +unsafe extern "C" fn file_get_parse_name(file: *mut ffi::GFile) -> *mut c_char { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + + let parse_name = imp.parse_name(); + parse_name.to_glib_full() +} + +unsafe extern "C" fn file_get_parent(file: *mut ffi::GFile) -> *mut ffi::GFile { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + + let res = imp.parent(); + if let Some(parent) = res { + parent.to_glib_full() + } else { + std::ptr::null_mut() + } +} + +unsafe extern "C" fn file_prefix_matches( + prefix: *mut ffi::GFile, + file: *mut ffi::GFile, +) -> glib::ffi::gboolean { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + + imp.has_prefix(&from_glib_borrow(prefix)).into_glib() +} + +unsafe extern "C" fn file_get_relative_path( + parent: *mut ffi::GFile, + descendant: *mut ffi::GFile, +) -> *mut c_char { + let instance = &*(parent as *mut T::Instance); + let imp = instance.imp(); + + let res = imp.relative_path(&from_glib_borrow(descendant)); + if let Some(relative_path) = res { + relative_path.to_glib_full() + } else { + std::ptr::null_mut() + } +} + +unsafe extern "C" fn file_resolve_relative_path( + file: *mut ffi::GFile, + relative_path: *const c_char, +) -> *mut ffi::GFile { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + + let resolved_path = + imp.resolve_relative_path(GString::from_glib_borrow(relative_path).as_ref()); + resolved_path.to_glib_full() +} + +unsafe extern "C" fn file_get_child_for_display_name( + file: *mut ffi::GFile, + display_name: *const c_char, + error: *mut *mut glib::ffi::GError, +) -> *mut ffi::GFile { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + + // check display name is a valid ut8 and handle error to avoid rust panicking if it is not + let basename = glib::ffi::g_filename_from_utf8( + display_name, + -1, + std::ptr::null_mut(), + std::ptr::null_mut(), + std::ptr::null_mut(), + ); + if basename.is_null() { + if !error.is_null() { + *error = Error::new::(IOErrorEnum::InvalidFilename, "Invalid filename") + .to_glib_full(); + } + return std::ptr::null_mut(); + } + + let res = imp.child_for_display_name(&GString::from_glib_borrow(display_name)); + match res { + Ok(child) => child.to_glib_full(), + Err(err) => { + if !error.is_null() { + *error = err.to_glib_full() + } + std::ptr::null_mut() + } + } +} + +unsafe extern "C" fn file_enumerate_children( + file: *mut ffi::GFile, + attributes: *const c_char, + flags: ffi::GFileQueryInfoFlags, + cancellable: *mut ffi::GCancellable, + error: *mut *mut glib::ffi::GError, +) -> *mut ffi::GFileEnumerator { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + let cancellable = Option::::from_glib_none(cancellable); + + let res = imp.enumerate_children( + &GString::from_glib_borrow(attributes), + from_glib(flags), + cancellable.as_ref(), + ); + match res { + Ok(enumerator) => enumerator.to_glib_full(), + Err(err) => { + if !error.is_null() { + *error = err.to_glib_full() + } + std::ptr::null_mut() + } + } +} + +unsafe extern "C" fn file_query_info( + file: *mut ffi::GFile, + attributes: *const c_char, + flags: ffi::GFileQueryInfoFlags, + cancellable: *mut ffi::GCancellable, + error: *mut *mut glib::ffi::GError, +) -> *mut ffi::GFileInfo { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + let cancellable = Option::::from_glib_none(cancellable); + + let res = imp.query_info( + &GString::from_glib_borrow(attributes), + from_glib(flags), + cancellable.as_ref(), + ); + match res { + Ok(file_info) => file_info.to_glib_full(), + Err(err) => { + if !error.is_null() { + *error = err.to_glib_full() + } + std::ptr::null_mut() + } + } +} + +unsafe extern "C" fn file_query_filesystem_info( + file: *mut ffi::GFile, + attributes: *const c_char, + cancellable: *mut ffi::GCancellable, + error: *mut *mut glib::ffi::GError, +) -> *mut ffi::GFileInfo { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + let cancellable = Option::::from_glib_none(cancellable); + + let res = + imp.query_filesystem_info(&GString::from_glib_borrow(attributes), cancellable.as_ref()); + match res { + Ok(file_info) => file_info.to_glib_full(), + Err(err) => { + if !error.is_null() { + *error = err.to_glib_full() + } + std::ptr::null_mut() + } + } +} + +unsafe extern "C" fn file_find_enclosing_mount( + file: *mut ffi::GFile, + cancellable: *mut ffi::GCancellable, + error: *mut *mut glib::ffi::GError, +) -> *mut ffi::GMount { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + let cancellable = Option::::from_glib_none(cancellable); + + let res = imp.find_enclosing_mount(cancellable.as_ref()); + match res { + Ok(mount) => mount.to_glib_full(), + Err(err) => { + if !error.is_null() { + *error = err.to_glib_full() + } + std::ptr::null_mut() + } + } +} + +unsafe extern "C" fn file_set_display_name( + file: *mut ffi::GFile, + display_name: *const c_char, + cancellable: *mut ffi::GCancellable, + error: *mut *mut glib::ffi::GError, +) -> *mut ffi::GFile { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + let cancellable = Option::::from_glib_none(cancellable); + + let res = imp.set_display_name( + &GString::from_glib_borrow(display_name), + cancellable.as_ref(), + ); + match res { + Ok(renamed_file) => renamed_file.to_glib_full(), + Err(err) => { + if !error.is_null() { + *error = err.to_glib_full() + } + std::ptr::null_mut() + } + } +} + +unsafe extern "C" fn file_query_settable_attributes( + file: *mut ffi::GFile, + cancellable: *mut ffi::GCancellable, + error: *mut *mut glib::ffi::GError, +) -> *mut ffi::GFileAttributeInfoList { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + let cancellable = Option::::from_glib_none(cancellable); + + let res = imp.query_settable_attributes(cancellable.as_ref()); + match res { + Ok(settable_attributes) => settable_attributes.to_glib_full(), + Err(err) => { + if !error.is_null() { + *error = err.to_glib_full() + } + std::ptr::null_mut() + } + } +} + +unsafe extern "C" fn file_query_writable_namespaces( + file: *mut ffi::GFile, + cancellable: *mut ffi::GCancellable, + error: *mut *mut glib::ffi::GError, +) -> *mut ffi::GFileAttributeInfoList { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + let cancellable = Option::::from_glib_none(cancellable); + + let res = imp.query_writable_namespaces(cancellable.as_ref()); + match res { + Ok(writable_namespaces) => writable_namespaces.to_glib_full(), + Err(err) => { + if !error.is_null() { + *error = err.to_glib_full() + } + std::ptr::null_mut() + } + } +} + +unsafe extern "C" fn file_set_attribute( + file: *mut ffi::GFile, + attribute: *const c_char, + type_: ffi::GFileAttributeType, + value_p: glib::ffi::gpointer, + flags: ffi::GFileQueryInfoFlags, + cancellable: *mut ffi::GCancellable, + error: *mut *mut glib::ffi::GError, +) -> glib::ffi::gboolean { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + let cancellable = Option::::from_glib_none(cancellable); + + let res_ = imp.set_attribute( + &GString::from_glib_borrow(attribute), + FileAttributeValue::for_pointer(from_glib(type_), value_p), + from_glib(flags), + cancellable.as_ref(), + ); + + match res_ { + Ok(_) => true.into_glib(), + Err(err) => { + if !error.is_null() { + *error = err.to_glib_full() + } + false.into_glib() + } + } +} + +unsafe extern "C" fn file_set_attributes_from_info( + file: *mut ffi::GFile, + info: *mut ffi::GFileInfo, + flags: ffi::GFileQueryInfoFlags, + cancellable: *mut ffi::GCancellable, + error: *mut *mut glib::ffi::GError, +) -> glib::ffi::gboolean { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + let cancellable = Option::::from_glib_none(cancellable); + + let res_ = imp.set_attributes_from_info( + &from_glib_borrow(info), + from_glib(flags), + cancellable.as_ref(), + ); + match res_ { + Ok(_) => true.into_glib(), + Err(err) => { + if !error.is_null() { + *error = err.to_glib_full() + } + false.into_glib() + } + } +} + +unsafe extern "C" fn file_read_fn( + file: *mut ffi::GFile, + cancellable: *mut ffi::GCancellable, + error: *mut *mut glib::ffi::GError, +) -> *mut ffi::GFileInputStream { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + let cancellable = Option::::from_glib_none(cancellable); + + let res_ = imp.read_fn(cancellable.as_ref()); + match res_ { + Ok(input_stream) => input_stream.to_glib_full(), + Err(err) => { + if !error.is_null() { + *error = err.to_glib_full() + } + std::ptr::null_mut() + } + } +} + +unsafe extern "C" fn file_append_to( + file: *mut ffi::GFile, + flags: ffi::GFileCreateFlags, + cancellable: *mut ffi::GCancellable, + error: *mut *mut glib::ffi::GError, +) -> *mut ffi::GFileOutputStream { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + let cancellable = Option::::from_glib_none(cancellable); + + let res_ = imp.append_to(from_glib(flags), cancellable.as_ref()); + match res_ { + Ok(output_stream) => output_stream.to_glib_full(), + Err(err) => { + if !error.is_null() { + *error = err.to_glib_full() + } + std::ptr::null_mut() + } + } +} + +unsafe extern "C" fn file_create( + file: *mut ffi::GFile, + flags: ffi::GFileCreateFlags, + cancellable: *mut ffi::GCancellable, + error: *mut *mut glib::ffi::GError, +) -> *mut ffi::GFileOutputStream { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + let cancellable = Option::::from_glib_none(cancellable); + + let res_ = imp.create(from_glib(flags), cancellable.as_ref()); + match res_ { + Ok(output_stream) => output_stream.to_glib_full(), + Err(err) => { + if !error.is_null() { + *error = err.to_glib_full() + } + std::ptr::null_mut() + } + } +} + +unsafe extern "C" fn file_replace( + file: *mut ffi::GFile, + etag: *const c_char, + make_backup: glib::ffi::gboolean, + flags: ffi::GFileCreateFlags, + cancellable: *mut ffi::GCancellable, + error: *mut *mut glib::ffi::GError, +) -> *mut ffi::GFileOutputStream { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + let etag = Option::::from_glib_none(etag); + let cancellable = Option::::from_glib_none(cancellable); + + let res_ = imp.replace( + etag.as_ref().map(|etag| etag.as_str()), + from_glib(make_backup), + from_glib(flags), + cancellable.as_ref(), + ); + match res_ { + Ok(output_stream) => output_stream.to_glib_full(), + Err(err) => { + if !error.is_null() { + *error = err.to_glib_full() + } + std::ptr::null_mut() + } + } +} + +unsafe extern "C" fn file_delete_file( + file: *mut ffi::GFile, + cancellable: *mut ffi::GCancellable, + error: *mut *mut glib::ffi::GError, +) -> glib::ffi::gboolean { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + let cancellable = Option::::from_glib_none(cancellable); + + let res = imp.delete(cancellable.as_ref()); + match res { + Ok(_) => true.into_glib(), + Err(err) => { + if !error.is_null() { + *error = err.to_glib_full() + } + false.into_glib() + } + } +} + +unsafe extern "C" fn file_trash( + file: *mut ffi::GFile, + cancellable: *mut ffi::GCancellable, + error: *mut *mut glib::ffi::GError, +) -> glib::ffi::gboolean { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + let cancellable = Option::::from_glib_none(cancellable); + + let res = imp.trash(cancellable.as_ref()); + match res { + Ok(_) => true.into_glib(), + Err(err) => { + if !error.is_null() { + *error = err.to_glib_full() + } + false.into_glib() + } + } +} + +unsafe extern "C" fn file_make_directory( + file: *mut ffi::GFile, + cancellable: *mut ffi::GCancellable, + error: *mut *mut glib::ffi::GError, +) -> glib::ffi::gboolean { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + let cancellable = Option::::from_glib_none(cancellable); + + let res = imp.make_directory(cancellable.as_ref()); + match res { + Ok(_) => true.into_glib(), + Err(err) => { + if !error.is_null() { + *error = err.to_glib_full() + } + false.into_glib() + } + } +} + +unsafe extern "C" fn file_make_symbolic_link( + file: *mut ffi::GFile, + symlink_value: *const c_char, + cancellable: *mut ffi::GCancellable, + error: *mut *mut glib::ffi::GError, +) -> glib::ffi::gboolean { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + let cancellable = Option::::from_glib_none(cancellable); + + let res = imp.make_symbolic_link( + GString::from_glib_borrow(symlink_value).as_ref(), + cancellable.as_ref(), + ); + match res { + Ok(_) => true.into_glib(), + Err(err) => { + if !error.is_null() { + *error = err.to_glib_full() + } + false.into_glib() + } + } +} + +unsafe extern "C" fn file_copy( + source: *mut ffi::GFile, + destination: *mut ffi::GFile, + flags: ffi::GFileCopyFlags, + cancellable: *mut ffi::GCancellable, + progress_callback: ffi::GFileProgressCallback, + progress_callback_data: glib::ffi::gpointer, + error: *mut *mut glib::ffi::GError, +) -> glib::ffi::gboolean { + let cancellable = Option::::from_glib_none(cancellable); + let mut progress_callback = progress_callback.map(|callback| { + move |current_num_bytes, total_num_bytes| unsafe { + callback(current_num_bytes, total_num_bytes, progress_callback_data) + } + }); + + let res = T::copy( + &from_glib_borrow(source), + &from_glib_borrow(destination), + from_glib(flags), + cancellable.as_ref(), + progress_callback + .as_mut() + .map(|callback| callback as &mut dyn FnMut(i64, i64)), + ); + match res { + Ok(_) => true.into_glib(), + Err(err) => { + if !error.is_null() { + *error = err.to_glib_full() + } + false.into_glib() + } + } +} + +unsafe extern "C" fn file_move( + source: *mut ffi::GFile, + destination: *mut ffi::GFile, + flags: ffi::GFileCopyFlags, + cancellable: *mut ffi::GCancellable, + progress_callback: ffi::GFileProgressCallback, + progress_callback_data: glib::ffi::gpointer, + error: *mut *mut glib::ffi::GError, +) -> glib::ffi::gboolean { + let cancellable = Option::::from_glib_none(cancellable); + let mut progress_callback = progress_callback.map(|callback| { + move |current_num_bytes, total_num_bytes| unsafe { + callback(current_num_bytes, total_num_bytes, progress_callback_data) + } + }); + + let res = T::move_( + &from_glib_borrow(source), + &from_glib_borrow(destination), + from_glib(flags), + cancellable.as_ref(), + progress_callback + .as_mut() + .map(|callback| callback as &mut dyn FnMut(i64, i64)), + ); + match res { + Ok(_) => true.into_glib(), + Err(err) => { + if !error.is_null() { + *error = err.to_glib_full() + } + false.into_glib() + } + } +} + +unsafe extern "C" fn file_mount_mountable( + file: *mut ffi::GFile, + flags: ffi::GMountMountFlags, + mount_operation: *mut ffi::GMountOperation, + cancellable: *mut ffi::GCancellable, + callback: ffi::GAsyncReadyCallback, + user_data: glib::ffi::gpointer, +) { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + let mount_operation = Option::::from_glib_none(mount_operation); + let cancellable = Option::::from_glib_none(cancellable); + let callback = callback.map(|callback| { + move |source_object: &Object, res: &AsyncResult| unsafe { + callback( + source_object.to_glib_none().0, + res.to_glib_none().0, + user_data, + ) + } + }); + + imp.mount_mountable( + from_glib(flags), + mount_operation.as_ref(), + cancellable.as_ref(), + callback, + ); +} + +unsafe extern "C" fn file_mount_mountable_finish( + file: *mut ffi::GFile, + res: *mut ffi::GAsyncResult, + error: *mut *mut glib::ffi::GError, +) -> *mut ffi::GFile { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + let result: &AsyncResult = &from_glib_borrow(res); + + let res_ = imp.mount_mountable_finish(result); + match res_ { + Ok(mounted) => mounted.to_glib_full(), + Err(err) => { + if !error.is_null() { + *error = err.to_glib_full() + } + std::ptr::null_mut() + } + } +} + +unsafe extern "C" fn file_unmount_mountable( + file: *mut ffi::GFile, + flags: ffi::GMountUnmountFlags, + cancellable: *mut ffi::GCancellable, + callback: ffi::GAsyncReadyCallback, + user_data: glib::ffi::gpointer, +) { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + let cancellable = Option::::from_glib_none(cancellable); + let callback = callback.map(|callback| { + move |source_object: &Object, res: &AsyncResult| unsafe { + callback( + source_object.to_glib_none().0, + res.to_glib_none().0, + user_data, + ) + } + }); + + imp.unmount_mountable(from_glib(flags), cancellable.as_ref(), callback); +} + +unsafe extern "C" fn file_unmount_mountable_finish( + file: *mut ffi::GFile, + res: *mut ffi::GAsyncResult, + error: *mut *mut glib::ffi::GError, +) -> glib::ffi::gboolean { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + let result: &AsyncResult = &from_glib_borrow(res); + + let res_ = imp.unmount_mountable_finish(result); + match res_ { + Ok(_) => true.into_glib(), + Err(err) => { + if !error.is_null() { + *error = err.to_glib_full() + } + false.into_glib() + } + } +} + +unsafe extern "C" fn file_eject_mountable( + file: *mut ffi::GFile, + flags: ffi::GMountUnmountFlags, + cancellable: *mut ffi::GCancellable, + callback: ffi::GAsyncReadyCallback, + user_data: glib::ffi::gpointer, +) { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + let cancellable = Option::::from_glib_none(cancellable); + let callback = callback.map(|callback| { + move |source_object: &Object, res: &AsyncResult| unsafe { + callback( + source_object.to_glib_none().0, + res.to_glib_none().0, + user_data, + ) + } + }); + + imp.eject_mountable(from_glib(flags), cancellable.as_ref(), callback); +} + +unsafe extern "C" fn file_eject_mountable_finish( + file: *mut ffi::GFile, + res: *mut ffi::GAsyncResult, + error: *mut *mut glib::ffi::GError, +) -> glib::ffi::gboolean { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + let result: &AsyncResult = &from_glib_borrow(res); + + let res_ = imp.eject_mountable_finish(result); + match res_ { + Ok(_) => true.into_glib(), + Err(err) => { + if !error.is_null() { + *error = err.to_glib_full() + } + false.into_glib() + } + } +} + +unsafe extern "C" fn file_mount_enclosing_volume( + file: *mut ffi::GFile, + flags: ffi::GMountMountFlags, + mount_operation: *mut ffi::GMountOperation, + cancellable: *mut ffi::GCancellable, + callback: ffi::GAsyncReadyCallback, + user_data: glib::ffi::gpointer, +) { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + let mount_operation = Option::::from_glib_none(mount_operation); + let cancellable = Option::::from_glib_none(cancellable); + let callback = callback.map(|callback| { + move |source_object: &Object, res: &AsyncResult| unsafe { + callback( + source_object.to_glib_none().0, + res.to_glib_none().0, + user_data, + ) + } + }); + + imp.mount_enclosing_volume( + from_glib(flags), + mount_operation.as_ref(), + cancellable.as_ref(), + callback, + ); +} + +unsafe extern "C" fn file_mount_enclosing_volume_finish( + file: *mut ffi::GFile, + res: *mut ffi::GAsyncResult, + error: *mut *mut glib::ffi::GError, +) -> glib::ffi::gboolean { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + let result: &AsyncResult = &from_glib_borrow(res); + + let res_ = imp.mount_enclosing_volume_finish(result); + match res_ { + Ok(_) => true.into_glib(), + Err(err) => { + if !error.is_null() { + *error = err.to_glib_full() + } + false.into_glib() + } + } +} + +unsafe extern "C" fn file_monitor_dir( + file: *mut ffi::GFile, + flags: ffi::GFileMonitorFlags, + cancellable: *mut ffi::GCancellable, + error: *mut *mut glib::ffi::GError, +) -> *mut ffi::GFileMonitor { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + let cancellable = Option::::from_glib_none(cancellable); + + let res = imp.monitor_dir(from_glib(flags), cancellable.as_ref()); + match res { + Ok(monitor) => monitor.to_glib_full(), + Err(err) => { + if !error.is_null() { + *error = err.to_glib_full() + } + std::ptr::null_mut() + } + } +} + +unsafe extern "C" fn file_monitor_file( + file: *mut ffi::GFile, + flags: ffi::GFileMonitorFlags, + cancellable: *mut ffi::GCancellable, + error: *mut *mut glib::ffi::GError, +) -> *mut ffi::GFileMonitor { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + let cancellable = Option::::from_glib_none(cancellable); + + let res = imp.monitor_file(from_glib(flags), cancellable.as_ref()); + match res { + Ok(monitor) => monitor.to_glib_full(), + Err(err) => { + if !error.is_null() { + *error = err.to_glib_full() + } + std::ptr::null_mut() + } + } +} + +unsafe extern "C" fn file_open_readwrite( + file: *mut ffi::GFile, + cancellable: *mut ffi::GCancellable, + error: *mut *mut glib::ffi::GError, +) -> *mut ffi::GFileIOStream { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + let cancellable = Option::::from_glib_none(cancellable); + + let res = imp.open_readwrite(cancellable.as_ref()); + match res { + Ok(io_stream) => io_stream.to_glib_full(), + Err(err) => { + if !error.is_null() { + *error = err.to_glib_full() + } + std::ptr::null_mut() + } + } +} + +unsafe extern "C" fn file_create_readwrite( + file: *mut ffi::GFile, + flags: ffi::GFileCreateFlags, + cancellable: *mut ffi::GCancellable, + error: *mut *mut glib::ffi::GError, +) -> *mut ffi::GFileIOStream { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + let cancellable = Option::::from_glib_none(cancellable); + + let res = imp.create_readwrite(from_glib(flags), cancellable.as_ref()); + match res { + Ok(io_stream) => io_stream.to_glib_full(), + Err(err) => { + if !error.is_null() { + *error = err.to_glib_full() + } + std::ptr::null_mut() + } + } +} + +unsafe extern "C" fn file_replace_readwrite( + file: *mut ffi::GFile, + etag: *const c_char, + make_backup: glib::ffi::gboolean, + flags: ffi::GFileCreateFlags, + cancellable: *mut ffi::GCancellable, + error: *mut *mut glib::ffi::GError, +) -> *mut ffi::GFileIOStream { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + let etag = Option::::from_glib_none(etag); + let cancellable = Option::::from_glib_none(cancellable); + + let res_ = imp.replace_readwrite( + etag.as_ref().map(|etag| etag.as_str()), + from_glib(make_backup), + from_glib(flags), + cancellable.as_ref(), + ); + match res_ { + Ok(io_stream) => io_stream.to_glib_full(), + Err(err) => { + if !error.is_null() { + *error = err.to_glib_full() + } + std::ptr::null_mut() + } + } +} + +unsafe extern "C" fn file_start_mountable( + file: *mut ffi::GFile, + flags: ffi::GDriveStartFlags, + mount_operation: *mut ffi::GMountOperation, + cancellable: *mut ffi::GCancellable, + callback: ffi::GAsyncReadyCallback, + user_data: glib::ffi::gpointer, +) { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + let mount_operation = Option::::from_glib_none(mount_operation); + let cancellable = Option::::from_glib_none(cancellable); + let callback = callback.map(|callback| { + move |source_object: &Object, res: &AsyncResult| unsafe { + callback( + source_object.to_glib_none().0, + res.to_glib_none().0, + user_data, + ) + } + }); + + imp.start_mountable( + from_glib(flags), + mount_operation.as_ref(), + cancellable.as_ref(), + callback, + ); +} + +unsafe extern "C" fn file_start_mountable_finish( + file: *mut ffi::GFile, + res: *mut ffi::GAsyncResult, + error: *mut *mut glib::ffi::GError, +) -> glib::ffi::gboolean { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + let result: &AsyncResult = &from_glib_borrow(res); + + let res_ = imp.start_mountable_finish(result); + match res_ { + Ok(_) => true.into_glib(), + Err(err) => { + if !error.is_null() { + *error = err.to_glib_full() + } + false.into_glib() + } + } +} + +unsafe extern "C" fn file_stop_mountable( + file: *mut ffi::GFile, + flags: ffi::GMountUnmountFlags, + mount_operation: *mut ffi::GMountOperation, + cancellable: *mut ffi::GCancellable, + callback: ffi::GAsyncReadyCallback, + user_data: glib::ffi::gpointer, +) { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + let mount_operation = Option::::from_glib_none(mount_operation); + let cancellable = Option::::from_glib_none(cancellable); + let callback = callback.map(|callback| { + move |source_object: &Object, res: &AsyncResult| unsafe { + callback( + source_object.to_glib_none().0, + res.to_glib_none().0, + user_data, + ) + } + }); + + imp.stop_mountable( + from_glib(flags), + mount_operation.as_ref(), + cancellable.as_ref(), + callback, + ); +} + +unsafe extern "C" fn file_stop_mountable_finish( + file: *mut ffi::GFile, + res: *mut ffi::GAsyncResult, + error: *mut *mut glib::ffi::GError, +) -> glib::ffi::gboolean { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + let result: &AsyncResult = &from_glib_borrow(res); + + let res_ = imp.stop_mountable_finish(result); + match res_ { + Ok(_) => true.into_glib(), + Err(err) => { + if !error.is_null() { + *error = err.to_glib_full() + } + false.into_glib() + } + } +} + +unsafe extern "C" fn file_unmount_mountable_with_operation( + file: *mut ffi::GFile, + flags: ffi::GMountUnmountFlags, + mount_operation: *mut ffi::GMountOperation, + cancellable: *mut ffi::GCancellable, + callback: ffi::GAsyncReadyCallback, + user_data: glib::ffi::gpointer, +) { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + let mount_operation = Option::::from_glib_none(mount_operation); + let cancellable = Option::::from_glib_none(cancellable); + let callback = callback.map(|callback| { + move |source_object: &Object, res: &AsyncResult| unsafe { + callback( + source_object.to_glib_none().0, + res.to_glib_none().0, + user_data, + ) + } + }); + + imp.unmount_mountable_with_operation( + from_glib(flags), + mount_operation.as_ref(), + cancellable.as_ref(), + callback, + ); +} + +unsafe extern "C" fn file_unmount_mountable_with_operation_finish( + file: *mut ffi::GFile, + res: *mut ffi::GAsyncResult, + error: *mut *mut glib::ffi::GError, +) -> glib::ffi::gboolean { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + let result: &AsyncResult = &from_glib_borrow(res); + + let res_ = imp.unmount_mountable_with_operation_finish(result); + match res_ { + Ok(_) => true.into_glib(), + Err(err) => { + if !error.is_null() { + *error = err.to_glib_full() + } + false.into_glib() + } + } +} + +unsafe extern "C" fn file_eject_mountable_with_operation( + file: *mut ffi::GFile, + flags: ffi::GMountUnmountFlags, + mount_operation: *mut ffi::GMountOperation, + cancellable: *mut ffi::GCancellable, + callback: ffi::GAsyncReadyCallback, + user_data: glib::ffi::gpointer, +) { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + let mount_operation = Option::::from_glib_none(mount_operation); + let cancellable = Option::::from_glib_none(cancellable); + let callback = callback.map(|callback| { + move |source_object: &Object, res: &AsyncResult| unsafe { + callback( + source_object.to_glib_none().0, + res.to_glib_none().0, + user_data, + ) + } + }); + + imp.eject_mountable_with_operation( + from_glib(flags), + mount_operation.as_ref(), + cancellable.as_ref(), + callback, + ); +} + +unsafe extern "C" fn file_eject_mountable_with_operation_finish( + file: *mut ffi::GFile, + res: *mut ffi::GAsyncResult, + error: *mut *mut glib::ffi::GError, +) -> glib::ffi::gboolean { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + let result: &AsyncResult = &from_glib_borrow(res); + + let res_ = imp.eject_mountable_with_operation_finish(result); + match res_ { + Ok(_) => true.into_glib(), + Err(err) => { + if !error.is_null() { + *error = err.to_glib_full() + } + false.into_glib() + } + } +} + +unsafe extern "C" fn file_poll_mountable( + file: *mut ffi::GFile, + cancellable: *mut ffi::GCancellable, + callback: ffi::GAsyncReadyCallback, + user_data: glib::ffi::gpointer, +) { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + let cancellable = Option::::from_glib_none(cancellable); + let callback = callback.map(|callback| { + move |source_object: &Object, res: &AsyncResult| unsafe { + callback( + source_object.to_glib_none().0, + res.to_glib_none().0, + user_data, + ) + } + }); + + imp.poll_mountable(cancellable.as_ref(), callback) +} + +unsafe extern "C" fn file_poll_mountable_finish( + file: *mut ffi::GFile, + res: *mut ffi::GAsyncResult, + error: *mut *mut glib::ffi::GError, +) -> glib::ffi::gboolean { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + let result: &AsyncResult = &from_glib_borrow(res); + + let res_ = imp.poll_mountable_finish(result); + match res_ { + Ok(_) => true.into_glib(), + Err(err) => { + if !error.is_null() { + *error = err.to_glib_full() + } + false.into_glib() + } + } +} + +unsafe extern "C" fn file_measure_disk_usage( + file: *mut ffi::GFile, + flags: ffi::GFileMeasureFlags, + cancellable: *mut ffi::GCancellable, + progress_callback: ffi::GFileMeasureProgressCallback, + progress_callback_data: glib::ffi::gpointer, + disk_usage: *mut u64, + num_dirs: *mut u64, + num_files: *mut u64, + error: *mut *mut glib::ffi::GError, +) -> glib::ffi::gboolean { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + let cancellable = Option::::from_glib_none(cancellable); + let progress_callback = progress_callback.map(|callback| { + Box::new( + move |reporting: bool, current_size: u64, num_dirs: u64, num_files: u64| unsafe { + callback( + reporting.into_glib(), + current_size, + num_dirs, + num_files, + progress_callback_data, + ) + }, + ) + }); + + let res = imp.measure_disk_usage( + from_glib(flags), + cancellable.as_ref(), + progress_callback.map(|callback| callback as Box), + ); + match res { + Ok((disk_usage_, num_dirs_, num_files_)) => { + if !disk_usage.is_null() { + *disk_usage = disk_usage_ + } + if !num_dirs.is_null() { + *num_dirs = num_dirs_ + } + if !num_files.is_null() { + *num_files = num_files_ + } + true.into_glib() + } + Err(err) => { + if !error.is_null() { + *error = err.to_glib_full() + } + false.into_glib() + } + } +} + +#[cfg(feature = "v2_84")] +unsafe extern "C" fn file_query_exists( + file: *mut ffi::GFile, + cancellable: *mut ffi::GCancellable, +) -> glib::ffi::gboolean { + let instance = &*(file as *mut T::Instance); + let imp = instance.imp(); + let cancellable = Option::::from_glib_none(cancellable); + + let res = imp.query_exists(cancellable.as_ref()); + res.into_glib() +} diff --git a/gio/src/subclass/mod.rs b/gio/src/subclass/mod.rs index 609e3d57c64e..85d8ba5dc68e 100644 --- a/gio/src/subclass/mod.rs +++ b/gio/src/subclass/mod.rs @@ -4,6 +4,7 @@ mod action_group; mod action_map; mod application; mod async_initable; +mod file; mod file_enumerator; mod file_monitor; mod initable; @@ -25,6 +26,7 @@ pub mod prelude { action_map::{ActionMapImpl, ActionMapImplExt}, application::{ApplicationImpl, ApplicationImplExt}, async_initable::{AsyncInitableImpl, AsyncInitableImplExt}, + file::{FileImpl, FileImplExt}, file_enumerator::{FileEnumeratorImpl, FileEnumeratorImplExt}, file_monitor::{FileMonitorImpl, FileMonitorImplExt}, initable::{InitableImpl, InitableImplExt}, diff --git a/gio/tests/file.rs b/gio/tests/file.rs new file mode 100644 index 000000000000..94c3648008f9 --- /dev/null +++ b/gio/tests/file.rs @@ -0,0 +1,1840 @@ +// Take a look at the license at the top of the repository in the LICENSE file. +// +// The following tests rely on a custom type `MyLocalFile` that extends the existing GIO type `GLocalFile`. Both types implement the interface `gio::auto::File`. +// For each virtual method defined in interface `gio::ffi::GFileIface`, a test checks that `MyLocalFile` and `GLocalFile` return the same results. +// Note that a `MyLocalFile` instance is built explicitly by calling `glib::Object::builder` whereas a a `GLocalFile` instance is created by calling `gio::auto::File::for_path`. + +use std::path::PathBuf; + +use futures_channel::oneshot; + +use gio::{ + prelude::*, subclass::prelude::*, Cancellable, DriveStartFlags, File, FileCopyFlags, + FileCreateFlags, FileMeasureFlags, FileMonitor, FileMonitorEvent, FileMonitorFlags, + FileQueryInfoFlags, IOErrorEnum, MountMountFlags, MountOperation, MountUnmountFlags, +}; +use glib::translate::ToGlibPtr; + +// Binding of existing GIO type GLocalFile. +pub mod ffi { + use libc::c_char; + + #[derive(Copy, Clone)] + #[repr(C)] + pub struct GLocalFile { + pub parent_instance: glib::gobject_ffi::GObject, + pub filename: *mut c_char, + } + + #[derive(Copy, Clone)] + #[repr(C)] + pub struct GLocalFileClass { + pub parent_class: glib::gobject_ffi::GObjectClass, + } +} + +glib::wrapper! { + #[doc(alias = "GLocalFile")] + pub struct LocalFile(Object) @implements File; + + match fn { + type_ => || { + use std::sync::Once; + static ONCE: Once = Once::new(); + + // ensure type is initialized by calling `gio::auto::File::for_path` to create a `GLocalFile` instance. + ONCE.call_once(|| unsafe { + let _ = File::for_path("path"); + }); + glib::gobject_ffi::g_type_from_name("GLocalFile".to_glib_none().0) + }, + } +} + +pub trait LocalFileImpl: ObjectImpl + ObjectSubclass + IsA> {} + +unsafe impl IsSubclassable for LocalFile {} + +// Define `MyLocalFile` as a subclass of `GLocalFile`. +mod imp { + use super::*; + + #[derive(Default)] + pub struct MyLocalFile; + + #[glib::object_subclass] + impl ObjectSubclass for MyLocalFile { + const NAME: &'static str = "MyLocalFile"; + type Type = super::MyLocalFile; + type ParentType = super::LocalFile; + type Interfaces = (File,); + } + + // Handle property `path` to properly initialize `GLocalFile` field `filename` in the parent instance at creation time. + impl DerivedObjectProperties for MyLocalFile { + fn derived_properties() -> &'static [glib::ParamSpec] { + static PROPERTIES: ::std::sync::OnceLock<[glib::ParamSpec; 1]> = + ::std::sync::OnceLock::new(); + PROPERTIES.get_or_init(||[< ::Value as HasParamSpec> ::param_spec_builder()("path").write_only().build(),]) + } + + fn derived_property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { + panic!("missing getter for property {}", pspec.name()) + } + + #[allow(unreachable_code)] + fn derived_set_property(&self, id: usize, value: &glib::Value, pspec: &glib::ParamSpec) { + match id { + 1 => unsafe { + (*::GlibType, + >>::to_glib_none( + self.obj().upcast_ref::() + ) + .0) + .filename = value.get::().unwrap().to_glib_full() + }, + _ => panic!("missing setter for property {}", pspec.name()), + } + } + } + + #[glib::derived_properties] + impl ObjectImpl for MyLocalFile {} + + // Implements `FileImpl` with default implementation, which calls the parent's implementation. + impl FileImpl for MyLocalFile {} + + impl LocalFileImpl for MyLocalFile {} +} + +glib::wrapper! { + pub struct MyLocalFile(ObjectSubclass) @extends LocalFile, @implements File; +} + +#[allow(dead_code)] +mod file_utilities; +use file_utilities::Temp; + +#[cfg(unix)] +const MY_FILE: &str = "/my_file"; + +#[cfg(windows)] +const MY_FILE: &str = "c:\\my_file"; + +#[test] +fn file_dup() { + // invoke `MyLocalFile` implementation of `gio::ffi::GFileIface::dup` + let my_local_file = glib::Object::builder::() + .property("path", MY_FILE) + .build(); + let dup = my_local_file.dup(); + + // invoke `LocalFile` implementation of `gio::ffi::GFileIface::dup` + let expected = File::for_path(MY_FILE).dup(); + + // both results should equal + assert!(dup.equal(&expected)); +} + +// checker-ignore-item +#[test] +fn file_hash() { + // invoke `MyLocalFile` implementation of `gio::ffi::GFileIface::hash` + let my_local_file = glib::Object::builder::() + .property("path", MY_FILE) + .build(); + let hash = unsafe { + gio::ffi::g_file_hash( + , + >>::to_glib_none(&my_local_file) + .0 as glib::ffi::gconstpointer, + ) + }; + + // invoke `LocalFile` implementation of `gio::ffi::GFileIface::hash` + let expected = unsafe { + gio::ffi::g_file_hash( + >::to_glib_none(&File::for_path(MY_FILE)).0 + as glib::ffi::gconstpointer, + ) + }; + + // both hash values should equal + assert_eq!(hash, expected); +} + +#[test] +fn file_equal() { + // 2 instances of `MyLocalFile` with same path should equal + let my_local_file = glib::Object::builder::() + .property("path", MY_FILE) + .build(); + let expected = glib::Object::builder::() + .property("path", MY_FILE) + .build(); + assert!(my_local_file.equal(&expected)); + + // instances of `MyLocalFile` and of `LocalFile` with same path should not equal (because type is different) + let expected = File::for_path(MY_FILE); + assert!(!my_local_file.equal(&expected)); +} + +#[test] +fn file_is_native() { + // invoke `MyLocalFile` implementation of `gio::ffi::GFileIface::is_native` + let my_local_file = glib::Object::builder::() + .property("path", MY_FILE) + .build(); + let is_native = my_local_file.is_native(); + + // invoke `LocalFile` implementation of `gio::ffi::GFileIface::is_native` + let expected = File::for_path(MY_FILE).is_native(); + + // both results should equal + assert_eq!(is_native, expected); +} + +#[test] +fn file_has_uri_scheme() { + // invoke `MyLocalFile` implementation of `gio::ffi::GFileIface::has_uri_scheme` + let my_local_file = glib::Object::builder::() + .property("path", MY_FILE) + .build(); + let has_file = my_local_file.has_uri_scheme("file"); + let has_foo = my_local_file.has_uri_scheme("foo"); + + // invoke `LocalFile` implementation of `gio::ffi::GFileIface::has_uri_scheme` + let file = File::for_path(MY_FILE); + let expected_file = file.has_uri_scheme("file"); + let expected_foo = file.has_uri_scheme("foo"); + + // both results should equal + assert_eq!(has_file, expected_file); + assert_eq!(has_foo, expected_foo); +} + +#[test] +fn file_uri_scheme() { + // invoke `MyLocalFile` implementation of `gio::ffi::GFileIface::uri_scheme` + let my_local_file = glib::Object::builder::() + .property("path", MY_FILE) + .build(); + let uri_scheme = my_local_file.uri_scheme(); + + // invoke `LocalFile` implementation of `gio::ffi::GFileIface::uri_scheme` + let expected = File::for_path(MY_FILE).uri_scheme(); + + // both uri schemes should equal + assert_eq!(uri_scheme, expected); +} + +#[test] +fn file_basename() { + // invoke `MyLocalFile` implementation of `gio::ffi::GFileIface::basename` + let my_local_file = glib::Object::builder::() + .property("path", MY_FILE) + .build(); + let basename = my_local_file.basename(); + + // invoke `LocalFile` implementation of `gio::ffi::GFileIface::basename` + let expected = File::for_path(MY_FILE).basename(); + + // both basenames should equal + assert_eq!(basename, expected); +} + +#[test] +fn file_path() { + // invoke `MyLocalFile` implementation of `gio::ffi::GFileIface::path` + let my_local_file = glib::Object::builder::() + .property("path", MY_FILE) + .build(); + let path = my_local_file.path(); + + // invoke `LocalFile` implementation of `gio::ffi::GFileIface::path` + let expected = File::for_path(MY_FILE).path(); + + // both paths should equal + assert_eq!(path, expected); +} + +#[test] +fn file_uri() { + // invoke `MyLocalFile` implementation of `gio::ffi::GFileIface::uri` + let my_local_file = glib::Object::builder::() + .property("path", MY_FILE) + .build(); + let uri = my_local_file.uri(); + + // invoke `LocalFile` implementation of `gio::ffi::GFileIface::uri` + let expected = File::for_path(MY_FILE).uri(); + + // both uris should equal + assert_eq!(uri, expected); +} + +#[test] +fn file_parse_name() { + // invoke `MyLocalFile` implementation of `gio::ffi::GFileIface::parse_name` + let my_local_file = glib::Object::builder::() + .property("path", MY_FILE) + .build(); + let parse_name = my_local_file.parse_name(); + + // invoke `LocalFile` implementation of `gio::ffi::GFileIface::parse_name` + let expected = File::for_path(MY_FILE).parse_name(); + + // both parse names should equal + assert_eq!(parse_name, expected); +} + +#[test] +fn file_parent() { + // invoke `MyLocalFile` implementation of `gio::ffi::GFileIface::parent` + let my_local_file = glib::Object::builder::() + .property("path", "/my_parent/my_file") + .build(); + let res = my_local_file.parent(); + assert!(res.is_some(), "unexpected None"); + let parent = res.unwrap(); + + // invoke `LocalFile` implementation of `gio::ffi::GFileIface::parent` + let res = File::for_path("/my_parent/my_file").parent(); + assert!(res.is_some(), "unexpected None"); + let expected = res.unwrap(); + + // both parents should equal + assert!(parent.equal(&expected)); +} + +#[test] +fn file_has_prefix() { + // invoke `MyLocalFile` implementation of `gio::ffi::GFileIface::has_prefix` + let my_local_file = glib::Object::builder::() + .property("path", "/my_prefix/my_file") + .build(); + let my_local_prefix = glib::Object::builder::() + .property("path", "/my_prefix") + .build(); + let has_prefix = my_local_file.has_prefix(&my_local_prefix); + + // invoke `LocalFile` implementation of `gio::ffi::GFileIface::has_prefix` + let expected = File::for_path("/my_prefix/my_file").has_prefix(&File::for_path("/my_prefix")); + + // both results should equal + assert_eq!(has_prefix, expected); +} + +#[test] +fn file_relative_path() { + // invoke `MyLocalFile` implementation of `gio::ffi::GFileIface::relative_path` + let my_local_parent = glib::Object::builder::() + .property("path", "/my_parent") + .build(); + let my_local_descendant = glib::Object::builder::() + .property("path", "/my_parent/my_descendant") + .build(); + let relative_path = my_local_parent.relative_path(&my_local_descendant); + + // invoke `LocalFile` implementation of `gio::ffi::GFileIface::relative_path` + let expected = + File::for_path("/my_parent").relative_path(&File::for_path("/my_parent/my_descendant")); + + // both relative paths should equal + assert_eq!(relative_path, expected); +} + +#[test] +fn file_resolve_relative_path() { + // invoke `MyLocalFile` implementation of `gio::ffi::GFileIface::resolve_relative_path` + let my_local_prefix = glib::Object::builder::() + .property("path", "/my_prefix") + .build(); + let resolved_path = my_local_prefix.resolve_relative_path("my_file"); + + // invoke `LocalFile` implementation of `gio::ffi::GFileIface::resolve_relative_path` + let expected = File::for_path("/my_prefix").resolve_relative_path("my_file"); + + // both resolved path result should equal + assert!(resolved_path.equal(&expected)); +} + +#[test] +fn file_child_for_display_name() { + // invoke `MyLocalFile` implementation of `gio::ffi::GFileIface::child_for_display_name` + let my_local_parent = glib::Object::builder::() + .property("path", "/my_parent") + .build(); + let res = my_local_parent.child_for_display_name("my_file"); + assert!(res.is_ok(), "{}", res.err().unwrap()); + let child = res.unwrap(); + + // invoke `LocalFile` implementation of `gio::ffi::GFileIface::child_for_display_name` + let res = File::for_path("/my_parent").child_for_display_name("my_file"); + assert!(res.is_ok(), "{}", res.err().unwrap()); + let expected = res.unwrap(); + + // both children should equal + assert!(child.equal(&expected)) +} + +#[test] +fn file_enumerate_children() { + // temporary dir and file are deleted when variables go out of scope + let my_temp_dir = Temp::make_dir("enumerate_children_XXXXXX"); + let _my_temp_file = my_temp_dir.create_file_child("my_file_XXXXXX"); + + // invoke `MyLocalFile` implementation of `gio::ffi::GFileIface::enumerate_children` + let my_local_dir = glib::Object::builder::() + .property("path", &my_temp_dir.path) + .build(); + let res = my_local_dir.enumerate_children("*", FileQueryInfoFlags::NONE, Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + let mut enumerator = res.unwrap(); + + // invoke `LocalFile` implementation of `gio::ffi::GFileIface::enumerate_children` + let res = File::for_path(&my_temp_dir.path).enumerate_children( + "*", + FileQueryInfoFlags::NONE, + Cancellable::NONE, + ); + assert!(res.is_ok(), "{}", res.err().unwrap()); + let expected_enumerator = res.unwrap(); + + // for each expected child + for res in expected_enumerator { + assert!(res.is_ok(), "{}", res.err().unwrap()); + let expected = res.unwrap(); + + // get next child from MyLocalFile's implementation + let res = enumerator.next(); + assert!(res.is_some(), "unexpected None"); + let res = res.unwrap(); + assert!(res.is_ok(), "{}", res.err().unwrap()); + let file_info = res.unwrap(); + + // both file infos should have the same attributes + for attr in expected.list_attributes(None) { + if attr != "standard::icon" && attr != "standard::symbolic-icon" { + assert_eq!( + file_info.attribute_as_string(&attr), + expected.attribute_as_string(&attr), + "attribute: {}", + &attr + ); + } + } + } +} + +#[test] +fn file_query_info() { + // temporary file is deleted when the variable goes out of scope + let (my_temp_file, _) = Temp::create_file("query_info_XXXXXX"); + + // invoke `MyLocalFile` implementation of `gio::ffi::GFileIface::query_info` + let my_local_file = glib::Object::builder::() + .property("path", &my_temp_file.path) + .build(); + let res = my_local_file.query_info("*", FileQueryInfoFlags::NONE, Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + let my_local_file_info = res.unwrap(); + + // invoke `LocalFile` implementation of `gio::ffi::GFileIface::query_info` + let res = File::for_path(&my_temp_file.path).query_info( + "*", + FileQueryInfoFlags::NONE, + Cancellable::NONE, + ); + assert!(res.is_ok(), "{}", res.err().unwrap()); + let expected = res.unwrap(); + + // both file infos should have the same attributes + for attr in expected.list_attributes(None) { + if attr != "standard::icon" && attr != "standard::symbolic-icon" { + assert_eq!( + my_local_file_info.attribute_as_string(&attr), + expected.attribute_as_string(&attr), + "attribute: {}", + &attr + ); + } + } +} + +#[test] +fn file_query_filesystem_info() { + // temporary file is deleted when the variable goes out of scope + let (my_temp_file, _) = Temp::create_file("query_filesystem_info_XXXXXX"); + + // invoke `MyLocalFile` implementation of `gio::ffi::GFileIface::query_filesystem_info` + let my_local_file = glib::Object::builder::() + .property("path", &my_temp_file.path) + .build(); + let res = my_local_file.query_filesystem_info("*", Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + let file_info = res.unwrap(); + + // invoke `LocalFile` implementation of `gio::ffi::GFileIface::query_filesystem_info` + let res = File::for_path(&my_temp_file.path).query_filesystem_info("*", Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + let expected = res.unwrap(); + + // both file infos should have the same attributes + for attr in expected.list_attributes(None) { + if attr != "filesystem::free" && attr != "filesystem::used" { + assert_eq!( + file_info.attribute_as_string(&attr), + expected.attribute_as_string(&attr), + "attribute: {}", + &attr + ); + } + } +} + +#[test] +fn file_find_enclosing_mount() { + // temporary dir is deleted when the variable goes out of scope + let my_temp_dir = Temp::make_dir("find_enclosing_mount_XXXXXX"); + + // invoke `MyLocalFile` implementation of `gio::ffi::GFileIface::find_enclosing_mount` + let my_local_dir = glib::Object::builder::() + .property("path", &my_temp_dir.path) + .build(); + let res = my_local_dir.find_enclosing_mount(Cancellable::NONE); + + // invoke `LocalFile` implementation of `gio::ffi::GFileIface::find_enclosing_mount` + let expected = File::for_path(&my_temp_dir.path).find_enclosing_mount(Cancellable::NONE); + + // both results should equal + assert_eq!( + res.map(|mount| mount.name()) + .map_err(|err| err.kind::()), + expected + .map(|mount| mount.name()) + .map_err(|err| err.kind::()) + ); +} + +#[test] +fn file_set_display_name() { + // temporary file is deleted when the variable goes out of scope + let (my_temp_file, _) = Temp::create_file("set_display_name_XXXXXX"); + + // invoke `MyLocalFile` implementation of `gio::ffi::GFileIface::set_display_name` + let my_local_file = glib::Object::builder::() + .property("path", &my_temp_file.path) + .build(); + let new_name = format!("{}_new_name", my_temp_file.basename); + let res = my_local_file.set_display_name(&new_name, Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + let file_renamed = res.unwrap(); + assert_eq!( + file_renamed.path(), + Some(PathBuf::from(format!("{}_new_name", my_temp_file.path))) + ); + + // invoke `LocalFile` implementation of `gio::ffi::GFileIface::set_display_name` + let res = File::for_path(format!("{}_new_name", my_temp_file.path)) + .set_display_name(&my_temp_file.basename, Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + + // filename should be the original one + assert_eq!(res.unwrap().path(), Some(PathBuf::from(&my_temp_file.path))); +} + +#[test] +fn file_query_settable_attributes() { + // temporary file is deleted when the variable goes out of scope + let (my_temp_file, _) = Temp::create_file("query_settable_attributes_XXXXXX"); + + // invoke `MyLocalFile` implementation of `gio::ffi::GFileIface::query_settable_attributes` + let my_local_file = glib::Object::builder::() + .property("path", &my_temp_file.path) + .build(); + let res = my_local_file.query_settable_attributes(Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + let file_attr_infos = res.unwrap(); + + // invoke `LocalFile` implementation of `gio::ffi::GFileIface::query_settable_attributes` + let res = File::for_path(&my_temp_file.path).query_settable_attributes(Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + let expected = res.unwrap(); + + // both file attribute info lists should have the same attributes + for (expected, my_local_file_attr_info) in expected + .attributes() + .iter() + .zip(file_attr_infos.attributes().iter()) + { + assert_eq!(my_local_file_attr_info.name(), expected.name()); + } +} + +#[test] +fn file_query_writable_namespaces() { + // temporary file is deleted when the variable goes out of scope + let (my_temp_file, _) = Temp::create_file("query_writable_namespaces_XXXXXX"); + + // invoke `MyLocalFile` implementation of `gio::ffi::GFileIface::query_writable_namespaces` + let my_local_file = glib::Object::builder::() + .property("path", &my_temp_file.path) + .build(); + let res = my_local_file.query_writable_namespaces(Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + let file_attr_infos = res.unwrap(); + + // invoke `LocalFile` implementation of `gio::ffi::GFileIface::query_writable_namespaces` + let res = File::for_path(&my_temp_file.path).query_writable_namespaces(Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + let expected = res.unwrap(); + + // both file attribute info lists should have the same attributes + for (expected, my_local_file_attr_info) in expected + .attributes() + .iter() + .zip(file_attr_infos.attributes().iter()) + { + assert_eq!(my_local_file_attr_info.name(), expected.name()); + } +} + +#[test] +fn file_set_attribute() { + // temporary file is deleted when the variable goes out of scope + let (my_temp_file, _) = Temp::create_file("set_attribute_XXXXXX"); + + // invoke `MyLocalFile` implementation of `gio::ffi::GFileIface::set_attribute` + let my_local_file = glib::Object::builder::() + .property("path", &my_temp_file.path) + .build(); + #[cfg(unix)] + { + let res = my_local_file.set_attribute( + "xattr::my_string", + "value", + FileQueryInfoFlags::NONE, + Cancellable::NONE, + ); + assert!(res.is_ok(), "{}", res.err().unwrap()); + } + + let res = my_local_file.set_attribute( + "time::access", + 1u64, + FileQueryInfoFlags::NONE, + Cancellable::NONE, + ); + assert!(res.is_ok(), "{}", res.err().unwrap()); + + #[cfg(unix)] + { + let res = my_local_file.set_attribute( + "time::access-nsec", + 1u32, + FileQueryInfoFlags::NONE, + Cancellable::NONE, + ); + assert!(res.is_ok(), "{}", res.err().unwrap()); + } + + // invoke `LocalFile` implementation of `gio::ffi::GFileIface::query_info` + let res = File::for_path(&my_temp_file.path).query_info( + "time::access,time::modified,time::access-nsec,xattr::my_string", + FileQueryInfoFlags::NONE, + Cancellable::NONE, + ); + assert!(res.is_ok(), "{}", res.err().unwrap()); + let file_info = res.unwrap(); + + // attributes should be the expected ones + #[cfg(unix)] + { + let my_string = file_info.attribute_string("xattr::my_string"); + assert_eq!(my_string.as_ref().map(glib::GString::as_str), Some("value")); + } + + let time_access = file_info.attribute_uint64("time::access"); + assert_eq!(time_access, 1); + + #[cfg(unix)] + { + let time_access_nsec = file_info.attribute_uint32("time::access-nsec"); + assert_eq!(time_access_nsec, 1); + } +} + +#[test] +fn file_set_attributes_from_info() { + // temporary file is deleted when the variable goes out of scope + let (my_temp_file, _) = Temp::create_file("set_attributes_from_info_XXXXXX"); + + // invoke `MyLocalFile` implementation of `gio::ffi::GFileIface::set_attributes_from_info` + let my_local_file = glib::Object::builder::() + .property("path", &my_temp_file.path) + .build(); + let res = my_local_file.query_info("time::access", FileQueryInfoFlags::NONE, Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + let my_local_file_info = res.unwrap(); + my_local_file_info.set_attribute_uint64("time::access", 1); + let res = my_local_file.set_attributes_from_info( + &my_local_file_info, + FileQueryInfoFlags::NONE, + Cancellable::NONE, + ); + assert!(res.is_ok(), "{}", res.err().unwrap()); + + // invoke `LocalFile` implementation of `gio::ffi::GFileIface::query_info` + let res = File::for_path(&my_temp_file.path).query_info( + "time::access", + FileQueryInfoFlags::NONE, + Cancellable::NONE, + ); + assert!(res.is_ok(), "{}", res.err().unwrap()); + let time_access = res.unwrap().attribute_uint64("time::access"); + + // time access should be 1 + assert_eq!(time_access, 1); +} + +#[test] +fn file_read_fn() { + // temporary file is deleted when the variable goes out of scope + let (my_temp_file, my_temp_file_io_stream) = Temp::create_file("read_fn_XXXXXX"); + { + // temporary output stream is closed when the variable output go out of scope + let output = my_temp_file_io_stream; + let res = output.write(b"foo", Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + assert_eq!(res.unwrap() as usize, b"foo".len()); + } + + // invoke `MyLocalFile` implementation of `gio::ffi::GFileIface::read` + let my_local_file = glib::Object::builder::() + .property("path", &my_temp_file.path) + .build(); + let res = my_local_file.read(Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + let input = res.unwrap(); + let mut buffer = vec![0; b"foo".len()]; + let res = input.read_all(&mut buffer, Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + assert_eq!(res.unwrap().0, buffer.capacity()); + let res = input.close(Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + + // invoke `LocalFile` implementation of `gio::ffi::GFileIface::read` + let mut expected = vec![0; b"foo".len()]; + let res = File::for_path(&my_temp_file.path).read(Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + let input = res.unwrap(); + let res = input.read_all(&mut expected, Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + assert_eq!(res.unwrap().0, expected.capacity()); + let res = input.close(Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + + // both read contents should equal + assert_eq!(buffer, expected); +} + +#[test] +fn file_append_to() { + // temporary file is deleted when the variable goes out of scope + let (my_temp_file, _) = Temp::create_file("append_to_XXXXXX"); + + // invoke `MyLocalFile` implementation of `gio::ffi::GFileIface::append_to` + let my_local_file = glib::Object::builder::() + .property("path", &my_temp_file.path) + .build(); + let res = my_local_file.append_to(FileCreateFlags::NONE, Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + let output = res.unwrap(); + let res = output.write(b"foo", Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + assert_eq!(res.unwrap() as usize, b"foo".len()); + let res = output.close(Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + + // invoke `LocalFile` implementation of `gio::ffi::GFileIface::read` + let res = File::for_path(&my_temp_file.path).read(Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + let input = res.unwrap(); + let mut buffer = vec![0; b"foo".len()]; + let res = input.read_all(&mut buffer, Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + assert_eq!(res.unwrap().0, buffer.len()); + + // read and appended contents should equal + assert_eq!(buffer, Vec::from(b"foo")); +} + +#[test] +fn file_create() { + // temporary file is deleted when the variable goes out of scope + let (my_temp_file, _) = Temp::create_file("create_XXXXXX"); + // delete temporary file so that we can recreate it + let res = my_temp_file.as_ref().unwrap().delete(Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + + // invoke `MyLocalFile` implementation of `gio::ffi::GFileIface::create` + let my_local_file = glib::Object::builder::() + .property("path", &my_temp_file.path) + .build(); + let res = my_local_file.create(FileCreateFlags::NONE, Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + let output = res.unwrap(); + let res = output.write(b"foo", Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + assert_eq!(res.unwrap() as usize, b"foo".len()); + let res = output.close(Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + + // invoke `LocalFile` implementation of `gio::ffi::GFileIface::read` + let res = File::for_path(&my_temp_file.path).read(Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + let input = res.unwrap(); + let mut buffer = vec![0; b"foo".len()]; + let res = input.read_all(&mut buffer, Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + assert_eq!(res.unwrap().0, buffer.len()); + + // read and created contents should equal + assert_eq!(buffer, Vec::from(b"foo")); +} + +#[test] +fn file_replace() { + // temporary file is deleted when the variable goes out of scope + let (my_temp_file, my_temp_file_io_stream) = Temp::create_file("replace_XXXXXX"); + { + // temporary output stream is closed when the variable output go out of scope + let output = my_temp_file_io_stream; + let res = output.write(b"foo", Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + assert_eq!(res.unwrap() as usize, b"foo".len()); + } + + // invoke `MyLocalFile` implementation of `gio::ffi::GFileIface::replace` + let my_local_file = glib::Object::builder::() + .property("path", &my_temp_file.path) + .build(); + let res = my_local_file.replace(None, false, FileCreateFlags::NONE, Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + let output = res.unwrap(); + let res = output.write(b"bar", Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + assert_eq!(res.unwrap() as usize, b"bar".len()); + let res = output.close(Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + + // invoke `LocalFile` implementation of `gio::ffi::GFileIface::read` + let res = File::for_path(&my_temp_file.path).read(Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + let input = res.unwrap(); + let mut buffer = vec![0; b"bar".len()]; + let res = input.read_all(&mut buffer, Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + assert_eq!(res.unwrap().0, buffer.len()); + + // read and replaced contents should equal + assert_eq!(buffer, Vec::from(b"bar")); +} + +#[test] +fn file_delete() { + // temporary file should be deleted when the variable goes out of scope... + let (mut my_temp_file, _) = Temp::create_file("delete_XXXXXX"); + // ... but we consume its file so that it won't be deleted when the variable goes out of scope + let _ = my_temp_file.take_file(); + + // invoke `MyLocalFile` implementation of `gio::ffi::GFileIface::delete` + let my_local_file = glib::Object::builder::() + .property("path", &my_temp_file.path) + .build(); + let res = my_local_file.delete(Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + + // invoke `LocalFile` implementation of `gio::ffi::GFileIface::query_exists` + let exist = File::for_path(&my_temp_file.path).query_exists(Cancellable::NONE); + + // file should not exist + assert!(!exist); +} + +#[test] +fn file_trash() { + // temporary file should be deleted when the variable goes out of scope... + let (mut my_temp_file, _) = Temp::create_file("trash_XXXXXX"); + // ... but we consume its file so that it won't be deleted when the variable goes out of scope + let _ = my_temp_file.take_file(); + + // invoke `MyLocalFile` implementation of `gio::ffi::GFileIface::trash` + let my_local_file = glib::Object::builder::() + .property("path", &my_temp_file.path) + .build(); + let res = my_local_file.trash(Cancellable::NONE); + assert!( + res.is_ok() + || res.as_ref().is_err_and(|err| err + .kind::() + .is_some_and(|err| err == IOErrorEnum::NotSupported)), + "{}", + res.err().unwrap() + ); + if res.is_ok() { + // continue test only if trashing on system is supported + // invoke `LocalFile` implementation of `gio::ffi::GFileIface::query_exists` + let my_file = File::for_path(&my_temp_file.path); + let exist = my_file.query_exists(Cancellable::NONE); + + // file should not exist + assert!(!exist); + } +} + +#[test] +fn file_make_directory() { + // temporary directory is deleted when the variable goes out of scope + let my_temp_dir = Temp::make_dir("make_directory_XXXXXX"); + // delete temporary directory so that we can recreate it + let res = my_temp_dir.as_ref().unwrap().delete(Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + + // invoke `MyLocalFile` implementation of `gio::ffi::GFileIface::make_directory` + let my_local_dir = glib::Object::builder::() + .property("path", &my_temp_dir.path) + .build(); + let res = my_local_dir.make_directory(Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + + // invoke `LocalFile` implementation of `gio::ffi::GFileIface::query_exists` + let exist = File::for_path(&my_temp_dir.path).query_exists(Cancellable::NONE); + + // file should exist + assert!(exist); +} + +#[test] +fn file_make_symbolic_link() { + // temporary symbolic link is deleted when the variable goes out of scope + let (my_temp_symbolic_link_target, _) = Temp::create_file("make_symbolic_link_target_XXXXXX"); + let (mut my_temp_symbolic_link, _) = Temp::create_file("make_symbolic_link_XXXXXX"); + // delete temporary file so that we can recreate it + let res = my_temp_symbolic_link + .as_ref() + .unwrap() + .delete(Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + + // invoke `MyLocalFile` implementation of `gio::ffi::GFileIface::make_symbolic_link` + let my_local_symbolic_link = glib::Object::builder::() + .property("path", &my_temp_symbolic_link.path) + .build(); + let res = my_local_symbolic_link + .make_symbolic_link(&my_temp_symbolic_link_target.path, Cancellable::NONE); + assert!( + res.is_ok() + || res.as_ref().is_err_and(|err| err + .kind::() + .is_some_and(|err| err == IOErrorEnum::NotSupported)), + "{}", + res.err().unwrap() + ); + if res.is_err() { + // if operation is not supported, temporary symbolic link is not created, + // so we consume its file so that it won't be deleted when the variable goes out of scope + my_temp_symbolic_link.take_file(); + } + + // invoke `LocalFile` implementation of `gio::ffi::GFileIface::query_exists` + let exist = File::for_path(&my_temp_symbolic_link.path).query_exists(Cancellable::NONE); + + // file should exist (or not) according to the operation result + assert_eq!(res.is_ok(), exist); +} + +#[test] +fn file_copy() { + // temporary source file is deleted when the variable goes out of scope + let (my_temp_source_file, my_source_file_io_stream) = Temp::create_file("copy_XXXXXX"); + { + // temporary output stream is closed when the variable output go out of scope + let output = my_source_file_io_stream; + let res = output.write(b"foo", Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + assert_eq!(res.unwrap() as usize, b"foo".len()); + } + // temporary destination file is deleted when the variable goes out of scope + let (my_temp_destination_file, _) = Temp::create_file("copy_XXXXXX"); + // delete temporary destination file so that we can recreate it + let res = my_temp_destination_file + .as_ref() + .unwrap() + .delete(Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + + // invoke `MyLocalFile` implementation of `gio::ffi::GFileIface::copy` + let my_local_source_file = glib::Object::builder::() + .property("path", &my_temp_source_file.path) + .build(); + let res = my_local_source_file.copy( + &File::for_path(&my_temp_destination_file.path), + FileCopyFlags::NONE, + Cancellable::NONE, + None, + ); + assert!(res.is_ok(), "{}", res.err().unwrap()); + + // invoke `LocalFile` implementation of `gio::ffi::GFileIface::read` + let res = File::for_path(&my_temp_destination_file.path).read(Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + let input = res.unwrap(); + let mut buffer = vec![0; b"foo".len()]; + let res = input.read_all(&mut buffer, Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + assert_eq!(res.unwrap().0, buffer.len()); + + // read and copied content should equal + assert_eq!(buffer, Vec::from(b"foo")); +} + +#[test] +fn file_move_() { + // temporary source file should be deleted when the variable goes out of scope... + let (mut my_temp_source_file, my_temp_source_file_io_stream) = Temp::create_file("move_XXXXXX"); + // ... but we consume its file so that it won't be deleted when the variable goes out of scope + let _ = my_temp_source_file.take_file(); + { + // temporary output stream is closed when the variable output go out of scope + let output = my_temp_source_file_io_stream; + let res = output.write(b"foo", Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + assert_eq!(res.unwrap() as usize, b"foo".len()); + } + // temporary destination file is deleted when the variable goes out of scope + let (my_temp_destination_file, _) = Temp::create_file("move_XXXXXX"); + // delete temporary destination file so that we can recreate it by renaming the temporary source file + let res = my_temp_destination_file + .as_ref() + .unwrap() + .delete(Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + + // invoke `MyLocalFile` implementation of `gio::ffi::GFileIface::move_` + let my_local_source_file = glib::Object::builder::() + .property("path", &my_temp_source_file.path) + .build(); + let res = my_local_source_file.move_( + &File::for_path(&my_temp_destination_file.path), + FileCopyFlags::NONE, + Cancellable::NONE, + None, + ); + assert!(res.is_ok(), "{}", res.err().unwrap()); + + // invoke `LocalFile` implementation of `gio::ffi::GFileIface::query_exists` + let exist = File::for_path(&my_temp_source_file.path).query_exists(Cancellable::NONE); + // source file should not exist + assert!(!exist); + let exist = File::for_path(&my_temp_destination_file.path).query_exists(Cancellable::NONE); + // destination file should exist + assert!(exist); + + // invoke `LocalFile` implementation of `gio::ffi::GFileIface::read` + let res = File::for_path(&my_temp_destination_file.path).read(Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + let input = res.unwrap(); + let mut buffer = vec![0; b"foo".len()]; + let res = input.read_all(&mut buffer, Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + assert_eq!(res.unwrap().0, buffer.len()); + + // read and moved content should equal + assert_eq!(buffer, Vec::from(b"foo")); +} + +#[test] +fn file_mount_mountable() { + // run test in a main context dedicated and configured as the thread default one + let _ = glib::MainContext::new().with_thread_default(|| { + // temporary dir is deleted when the variable goes out of scope + let my_temp_dir = Temp::make_dir("mount_mountable_XXXXXX"); + + // invoke `MyLocalFile` implementation of `gio::ffi::GFileIface::mount_mountable` + let my_local_dir = glib::Object::builder::() + .property("path", &my_temp_dir.path) + .build(); + let (tx, rx) = oneshot::channel(); + let res = glib::MainContext::ref_thread_default().block_on(async { + my_local_dir.mount_mountable( + MountMountFlags::NONE, + MountOperation::NONE, + Cancellable::NONE, + move |res| tx.send(res).unwrap(), + ); + rx.await.unwrap() + }); + + // invoke `LocalFile` implementation of `gio::ffi::GFileIface::mount_mountable` + let (tc, rx) = oneshot::channel(); + let expected = glib::MainContext::ref_thread_default().block_on(async { + File::for_path(&my_temp_dir.path).mount_mountable( + MountMountFlags::NONE, + MountOperation::NONE, + Cancellable::NONE, + move |res| tc.send(res).unwrap(), + ); + rx.await.unwrap() + }); + + // both results should equal + assert_eq!( + res.map_err(|err| err.kind::()), + expected.map_err(|err| err.kind::()) + ); + }); +} + +// checker-ignore-item +#[test] +fn file_unmount_mountable() { + // run test in a main context dedicated and configured as the thread default one + let _ = glib::MainContext::new().with_thread_default(|| { + // temporary dir is deleted when the variable goes out of scope + let my_temp_dir = Temp::make_dir("unmount_mountable_XXXXXX"); + + // implement the deprecated function unmount_mountable which is useful for this test + fn unmount_mountable, P: FnOnce(Result<(), glib::Error>) + 'static>( + file: &T, + flags: MountUnmountFlags, + cancellable: Option<&impl IsA>, + callback: P, + ) { + use glib::translate::{from_glib_full, IntoGlib}; + use std::boxed::Box as Box_; + let main_context = glib::MainContext::ref_thread_default(); + let is_main_context_owner = main_context.is_owner(); + let has_acquired_main_context = (!is_main_context_owner) + .then(|| main_context.acquire().ok()) + .flatten(); + assert!( + is_main_context_owner || has_acquired_main_context.is_some(), + "Async operations only allowed if the thread is owning the MainContext" + ); + + let user_data: Box_> = + Box_::new(glib::thread_guard::ThreadGuard::new(callback)); + unsafe extern "C" fn unmount_mountable_trampoline< + P: FnOnce(Result<(), glib::Error>) + 'static, + >( + _source_object: *mut glib::gobject_ffi::GObject, + res: *mut gio::ffi::GAsyncResult, + user_data: glib::ffi::gpointer, + ) { + let mut error = std::ptr::null_mut(); + gio::ffi::g_file_unmount_mountable_finish( + _source_object as *mut _, + res, + &mut error, + ); + let result = if error.is_null() { + Ok(()) + } else { + Err(from_glib_full(error)) + }; + let callback: Box_> = + Box_::from_raw(user_data as *mut _); + let callback: P = callback.into_inner(); + callback(result); + } + let callback = unmount_mountable_trampoline::

; + unsafe { + gio::ffi::g_file_unmount_mountable( + file.as_ref().to_glib_none().0, + flags.into_glib(), + cancellable.map(|p| p.as_ref()).to_glib_none().0, + Some(callback), + Box_::into_raw(user_data) as *mut _, + ); + } + } + + // invoke `MyLocalFile` implementation of `gio::ffi::GFileIface::unmount_mountable` + let my_local_dir = glib::Object::builder::() + .property("path", &my_temp_dir.path) + .build(); + let (tx, rx) = oneshot::channel(); + let res = glib::MainContext::ref_thread_default().block_on(async { + unmount_mountable( + &my_local_dir, + MountUnmountFlags::NONE, + Cancellable::NONE, + move |res| tx.send(res).unwrap(), + ); + rx.await.unwrap() + }); + + // invoke `LocalFile` implementation of `gio::ffi::GFileIface::unmount_mountable` + let (tx, rx) = oneshot::channel(); + let expected = glib::MainContext::ref_thread_default().block_on(async { + unmount_mountable( + &File::for_path(&my_temp_dir.path), + MountUnmountFlags::NONE, + Cancellable::NONE, + move |res| tx.send(res).unwrap(), + ); + rx.await.unwrap() + }); + + // both results should equal + assert_eq!( + res.map_err(|err| err.kind::()), + expected.map_err(|err| err.kind::()) + ); + }); +} + +// checker-ignore-item +#[test] +fn file_eject_mountable() { + // run test in a main context dedicated and configured as the thread default one + let _ = glib::MainContext::new().with_thread_default(|| { + // temporary dir is deleted when the variable goes out of scope + let my_temp_dir = Temp::make_dir("eject_mountable_XXXXXX"); + + // implement the deprecated function eject_mountable which is useful for this test + fn eject_mountable, P: FnOnce(Result<(), glib::Error>) + 'static>( + file: &T, + flags: MountUnmountFlags, + cancellable: Option<&impl IsA>, + callback: P, + ) { + use glib::translate::{from_glib_full, IntoGlib}; + use std::boxed::Box as Box_; + let main_context = glib::MainContext::ref_thread_default(); + let is_main_context_owner = main_context.is_owner(); + let has_acquired_main_context = (!is_main_context_owner) + .then(|| main_context.acquire().ok()) + .flatten(); + assert!( + is_main_context_owner || has_acquired_main_context.is_some(), + "Async operations only allowed if the thread is owning the MainContext" + ); + + let user_data: Box_> = + Box_::new(glib::thread_guard::ThreadGuard::new(callback)); + unsafe extern "C" fn eject_mountable_trampoline< + P: FnOnce(Result<(), glib::Error>) + 'static, + >( + _source_object: *mut glib::gobject_ffi::GObject, + res: *mut gio::ffi::GAsyncResult, + user_data: glib::ffi::gpointer, + ) { + let mut error = std::ptr::null_mut(); + gio::ffi::g_file_eject_mountable_finish(_source_object as *mut _, res, &mut error); + let result = if error.is_null() { + Ok(()) + } else { + Err(from_glib_full(error)) + }; + let callback: Box_> = + Box_::from_raw(user_data as *mut _); + let callback: P = callback.into_inner(); + callback(result); + } + let callback = eject_mountable_trampoline::

; + unsafe { + gio::ffi::g_file_eject_mountable( + file.as_ref().to_glib_none().0, + flags.into_glib(), + cancellable.map(|p| p.as_ref()).to_glib_none().0, + Some(callback), + Box_::into_raw(user_data) as *mut _, + ); + } + } + + // invoke `MyLocalFile` implementation of `gio::ffi::GFileIface::eject_mountable` + let my_local_dir = glib::Object::builder::() + .property("path", &my_temp_dir.path) + .build(); + let (tx, rx) = oneshot::channel(); + let res = glib::MainContext::ref_thread_default().block_on(async { + eject_mountable( + &my_local_dir, + MountUnmountFlags::NONE, + Cancellable::NONE, + move |res| tx.send(res).unwrap(), + ); + rx.await.unwrap() + }); + + // invoke `LocalFile` implementation of `gio::ffi::GFileIface::eject_mountable` + let (tx, rx) = oneshot::channel(); + let expected = glib::MainContext::ref_thread_default().block_on(async { + eject_mountable( + &File::for_path(&my_temp_dir.path), + MountUnmountFlags::NONE, + Cancellable::NONE, + move |res| tx.send(res).unwrap(), + ); + rx.await.unwrap() + }); + + // both results should equal + assert_eq!( + res.map_err(|err| err.kind::()), + expected.map_err(|err| err.kind::()) + ); + }); +} + +#[test] +fn file_mount_enclosing_volume() { + // run test in a main context dedicated and configured as the thread default one + let _ = glib::MainContext::new().with_thread_default(|| { + // temporary dir is deleted when the variable goes out of scope + let my_temp_dir = Temp::make_dir("mount_enclosing_volume_XXXXXX"); + + // invoke `MyLocalFile` implementation of `gio::ffi::GFileIface::mount_enclosing_volume` + let my_local_dir = glib::Object::builder::() + .property("path", &my_temp_dir.path) + .build(); + let (tx, rx) = oneshot::channel(); + let res = glib::MainContext::ref_thread_default().block_on(async { + my_local_dir.mount_enclosing_volume( + MountMountFlags::NONE, + MountOperation::NONE, + Cancellable::NONE, + move |res| tx.send(res).unwrap(), + ); + rx.await.unwrap() + }); + // invoke `LocalFile` implementation of `gio::ffi::GFileIface::mount_enclosing_volume` + let (tx, rx) = oneshot::channel(); + let expected = glib::MainContext::ref_thread_default().block_on(async { + File::for_path(&my_temp_dir.path).mount_enclosing_volume( + MountMountFlags::NONE, + MountOperation::NONE, + Cancellable::NONE, + move |res| tx.send(res).unwrap(), + ); + rx.await.unwrap() + }); + + // both results should equal + assert_eq!( + res.map_err(|err| err.kind::()), + expected.map_err(|err| err.kind::()) + ); + }); +} + +#[test] +fn file_monitor_dir() { + // run test in a main context dedicated and configured as the thread default one + let _ = glib::MainContext::new().with_thread_default(|| { + // temporary dir is deleted when the variable goes out of scope + let my_temp_dir = Temp::make_dir("monitor_dir_XXXXXX"); + + // utility function to create a directory monitor and a channel receiver + fn monitor_dir>( + directory: &T, + ) -> ( + FileMonitor, + async_channel::Receiver<(FileMonitorEvent, glib::GString, Option)>, + ) { + let res = directory.monitor_directory(FileMonitorFlags::NONE, Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + let monitor = res.unwrap(); + let rx = { + let (tx, rx) = async_channel::unbounded(); + monitor.connect_changed( + move |_: &FileMonitor, + file: &File, + other_file: Option<&File>, + event_type: FileMonitorEvent| { + let res = glib::MainContext::ref_thread_default().block_on(tx.send(( + event_type, + file.parse_name(), + other_file.map(File::parse_name), + ))); + assert!(res.is_ok(), "{}", res.err().unwrap()); + if event_type == FileMonitorEvent::Deleted { + tx.close(); + } + }, + ); + rx + }; + (monitor, rx) + } + + // invoke `MyLocalFile` implementation of `gio::ffi::GFileIface::monitor_dir` + let my_local_dir = glib::Object::builder::() + .property("path", &my_temp_dir.path) + .build(); + let (monitor, rx) = monitor_dir(&my_local_dir); + + // invoke `LocalFile` implementation of `gio::ffi::GFileIface::monitor_dir` + let my_dir = File::for_path(&my_temp_dir.path); + let (expected_monitor, expected_rx) = monitor_dir(&my_dir); + + { + // temporary file is deleted when the variable goes out of scope + let _my_temp_file1 = my_temp_dir.create_file_child("my_file_1_XXXXXX"); + } + + glib::MainContext::ref_thread_default().block_on(async { + let mut n = 0; + // for each expected event (from monitor returned by ffi::GLocalFile's implementation) + while let Ok(expected) = expected_rx.recv().await { + n += 1; + // get next event from monitor returned by MyLocalFile's implementation + let res = rx.recv().await; + assert!(res.is_ok(), "{}", res.err().unwrap()); + let event = res.unwrap(); + + // both events should equal + assert_eq!(event, expected); + } + + // at least 1 file event expected + assert!(n > 0, "at least 1 file event is expected"); + }); + + // cancel both monitors + assert!(monitor.cancel()); + assert!(expected_monitor.cancel()); + }); +} + +#[test] +fn file_monitor_file() { + // run test in a main context dedicated and configured as the thread default one + let _ = glib::MainContext::new().with_thread_default(|| { + // temporary file is deleted when variables go out of scope + let (my_temp_file, _) = Temp::create_file("monitor_file_XXXXXX"); + + // utility function to create a file monitor and a channel receiver + fn monitor_file>( + file: &T, + ) -> ( + FileMonitor, + async_channel::Receiver<(FileMonitorEvent, glib::GString, Option)>, + ) { + let res = file.monitor_file(FileMonitorFlags::NONE, Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + let monitor = res.unwrap(); + let rx = { + let (tx, rx) = async_channel::unbounded(); + monitor.connect_changed( + move |_: &FileMonitor, + file: &File, + other_file: Option<&File>, + event_type: FileMonitorEvent| { + let res = glib::MainContext::ref_thread_default().block_on(tx.send(( + event_type, + file.parse_name(), + other_file.map(File::parse_name), + ))); + assert!(res.is_ok(), "{}", res.err().unwrap()); + if event_type == FileMonitorEvent::ChangesDoneHint { + tx.close(); + } + }, + ); + rx + }; + (monitor, rx) + } + + // invoke `MyLocalFile` implementation of `gio::ffi::GFileIface::monitor_file` + let my_local_file = glib::Object::builder::() + .property("path", &my_temp_file.path) + .build(); + let (monitor, rx) = monitor_file(&my_local_file); + + // invoke `LocalFile` implementation of `gio::ffi::GFileIface::monitor_file` + let my_file = File::for_path(&my_temp_file.path); + let (expected_monitor, expected_rx) = monitor_file(&my_file); + + // modify the file + let res = my_local_file.append_to(FileCreateFlags::NONE, Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + let output = res.unwrap(); + let res = output.write(b"foo", Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + assert_eq!(res.unwrap() as usize, b"foo".len()); + let res = output.close(Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + + glib::MainContext::ref_thread_default().block_on(async { + let mut n = 0; + // for each expected event (from monitor returned by ffi::GLocalFile's implementation) + while let Ok(expected) = expected_rx.recv().await { + n += 1; + // get next event from monitor returned by MyLocalFile's implementation + let res = rx.recv().await; + assert!(res.is_ok(), "{}", res.err().unwrap()); + let event = res.unwrap(); + + // both event should equal + assert_eq!(event, expected); + } + + // at least 1 file event expected + assert!(n > 0, "at least 1 file event is expected"); + }); + + // cancel both monitors + assert!(monitor.cancel()); + assert!(expected_monitor.cancel()); + }); +} + +#[test] +fn file_open_readwrite() { + // temporary file is deleted when the variable goes out of scope + let (my_temp_file, _) = Temp::create_file("open_readwrite_XXXXXX"); + + // invoke `MyLocalFile` implementation of `gio::ffi::GFileIface::open_readwrite` + let my_local_file = glib::Object::builder::() + .property("path", &my_temp_file.path) + .build(); + let res = my_local_file.open_readwrite(Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + let io = res.unwrap(); + let output = io.output_stream(); + let res = output.write(b"foo", Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + assert_eq!(res.unwrap() as usize, b"foo".len()); + let res = io.close(Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + + // invoke `LocalFile` implementation of `gio::ffi::GFileIface::open_readwrite` + let res = File::for_path(&my_temp_file.path).open_readwrite(Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + let io = res.unwrap(); + let input = io.input_stream(); + let mut buffer = vec![0; b"foo".len()]; + let res = input.read_all(&mut buffer, Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + assert_eq!(res.unwrap().0, buffer.len()); + let res = io.close(Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + + // read and written contents should equal + assert_eq!(buffer, Vec::from(b"foo")); +} + +#[test] +fn file_create_readwrite() { + // temporary file is deleted when the variable goes out of scope + let (my_temp_file, _) = Temp::create_file("create_readwrite_XXXXXX"); + // delete temporary file so that we can recreate it + let res = my_temp_file.as_ref().unwrap().delete(Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + + // invoke `MyLocalFile` implementation of `gio::ffi::GFileIface::create_readwrite` + let my_local_file = glib::Object::builder::() + .property("path", &my_temp_file.path) + .build(); + let res = my_local_file.create_readwrite(FileCreateFlags::NONE, Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + let io = res.unwrap(); + let output = io.output_stream(); + let res = output.write(b"foo", Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + assert_eq!(res.unwrap() as usize, b"foo".len()); + let res = io.close(Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + + // invoke `LocalFile` implementation of `gio::ffi::GFileIface::open_readwrite` + let res = File::for_path(&my_temp_file.path).open_readwrite(Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + let io = res.unwrap(); + let input = io.input_stream(); + let mut buffer = vec![0; b"foo".len()]; + let res = input.read_all(&mut buffer, Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + assert_eq!(res.unwrap().0, buffer.len()); + let res = io.close(Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + + // read and written contents should equal + assert_eq!(buffer, Vec::from(b"foo")); +} + +#[test] +fn file_replace_readwrite() { + // temporary file is deleted when the variable goes out of scope + let (my_temp_file, my_temp_file_io_stream) = Temp::create_file("replace_readwrite_XXXXXX"); + { + // temporary output stream is closed when the variable output goes out of scope + let output = my_temp_file_io_stream; + let res = output.write(b"foo", Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + assert_eq!(res.unwrap() as usize, b"foo".len()); + } + + // invoke `MyLocalFile` implementation of `gio::ffi::GFileIface::replace_readwrite` + let my_local_file = glib::Object::builder::() + .property("path", &my_temp_file.path) + .build(); + let res = + my_local_file.replace_readwrite(None, false, FileCreateFlags::NONE, Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + let io = res.unwrap(); + let output = io.output_stream(); + let res = output.write(b"bar", Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + assert_eq!(res.unwrap() as usize, b"bar".len()); + let res = io.close(Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + + // invoke `LocalFile` implementation of `gio::ffi::GFileIface::open_readwrite` + let res = File::for_path(&my_temp_file.path).open_readwrite(Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + let io = res.unwrap(); + let input = io.input_stream(); + let mut buffer = vec![0; b"foo".len()]; + let res = input.read_all(&mut buffer, Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + assert_eq!(res.unwrap().0, buffer.len()); + let res = io.close(Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + + // read and replaced contents should equal + assert_eq!(buffer, Vec::from(b"bar")); +} + +#[test] +fn file_start_mountable() { + // run test in a main context dedicated and configured as the thread default one + let _ = glib::MainContext::new().with_thread_default(|| { + // temporary dir is deleted when the variable goes out of scope + let my_temp_dir = Temp::make_dir("start_mountable_XXXXXX"); + + // invoke `MyLocalFile` implementation of `gio::ffi::GFileIface::start_mountable` + let my_local_dir = glib::Object::builder::() + .property("path", &my_temp_dir.path) + .build(); + let (tx, rx) = oneshot::channel(); + let res = glib::MainContext::ref_thread_default().block_on(async { + my_local_dir.start_mountable( + DriveStartFlags::NONE, + MountOperation::NONE, + Cancellable::NONE, + move |res| tx.send(res).unwrap(), + ); + rx.await.unwrap() + }); + + // invoke `LocalFile` implementation of `gio::ffi::GFileIface::start_mountable` + let (tx, rx) = oneshot::channel(); + let expected = glib::MainContext::ref_thread_default().block_on(async { + File::for_path(&my_temp_dir.path).start_mountable( + DriveStartFlags::NONE, + MountOperation::NONE, + Cancellable::NONE, + move |res| tx.send(res).unwrap(), + ); + rx.await.unwrap() + }); + + // both results should equal + assert_eq!( + res.map_err(|err| err.kind::()), + expected.map_err(|err| err.kind::()) + ); + }); +} + +#[test] +fn file_stop_mountable() { + // run test in a main context dedicated and configured as the thread default one + let _ = glib::MainContext::new().with_thread_default(|| { + // temporary dir is deleted when the variable goes out of scope + let my_temp_dir = Temp::make_dir("stop_mountable_XXXXXX"); + + // invoke `MyLocalFile` implementation of `gio::ffi::GFileIface::stop_mountable` + let my_local_dir = glib::Object::builder::() + .property("path", &my_temp_dir.path) + .build(); + let (tx, rx) = oneshot::channel(); + let res = glib::MainContext::ref_thread_default().block_on(async { + my_local_dir.stop_mountable( + MountUnmountFlags::NONE, + MountOperation::NONE, + Cancellable::NONE, + move |res| tx.send(res).unwrap(), + ); + rx.await.unwrap() + }); + + // invoke `LocalFile` implementation of `gio::ffi::GFileIface::stop_mountable` + let (tx, rx) = oneshot::channel(); + let expected = glib::MainContext::ref_thread_default().block_on(async { + File::for_path(&my_temp_dir.path).stop_mountable( + MountUnmountFlags::NONE, + MountOperation::NONE, + Cancellable::NONE, + move |res| tx.send(res).unwrap(), + ); + rx.await.unwrap() + }); + + // both results should equal + assert_eq!( + res.map_err(|err| err.kind::()), + expected.map_err(|err| err.kind::()) + ); + }); +} + +#[test] +fn file_unmount_mountable_with_operation() { + // run test in a main context dedicated and configured as the thread default one + let _ = glib::MainContext::new().with_thread_default(|| { + // temporary dir is deleted when the variable goes out of scope + let my_temp_dir = Temp::make_dir("unmount_mountable_with_operation_XXXXXX"); + + // invoke `MyLocalFile` implementation of `gio::ffi::GFileIface::unmount_mountable_with_operation` + let my_local_dir = glib::Object::builder::() + .property("path", &my_temp_dir.path) + .build(); + let (tx, rx) = oneshot::channel(); + let res = glib::MainContext::ref_thread_default().block_on(async { + my_local_dir.unmount_mountable_with_operation( + MountUnmountFlags::NONE, + MountOperation::NONE, + Cancellable::NONE, + move |res| tx.send(res).unwrap(), + ); + rx.await.unwrap() + }); + + // invoke `LocalFile` implementation of `gio::ffi::GFileIface::unmount_mountable_with_operation` + let (tx, rx) = oneshot::channel(); + let expected = glib::MainContext::ref_thread_default().block_on(async { + File::for_path(&my_temp_dir.path).unmount_mountable_with_operation( + MountUnmountFlags::NONE, + MountOperation::NONE, + Cancellable::NONE, + move |res| tx.send(res).unwrap(), + ); + rx.await.unwrap() + }); + + // both results should equal + assert_eq!( + res.map_err(|err| err.kind::()), + expected.map_err(|err| err.kind::()) + ); + }); +} + +#[test] +fn file_eject_mountable_with_operation() { + // run test in a main context dedicated and configured as the thread default one + let _ = glib::MainContext::new().with_thread_default(|| { + // temporary dir is deleted when the variable goes out of scope + let my_temp_dir = Temp::make_dir("eject_mountable_with_operation_XXXXXX"); + + // invoke `MyLocalFile` implementation of `gio::ffi::GFileIface::eject_mountable_with_operation` + let my_local_dir = glib::Object::builder::() + .property("path", &my_temp_dir.path) + .build(); + let (tx, rx) = oneshot::channel(); + let res = glib::MainContext::ref_thread_default().block_on(async { + my_local_dir.eject_mountable_with_operation( + MountUnmountFlags::NONE, + MountOperation::NONE, + Cancellable::NONE, + move |res| tx.send(res).unwrap(), + ); + rx.await.unwrap() + }); + + // invoke `LocalFile` implementation of `gio::ffi::GFileIface::eject_mountable_with_operation` + let (tx, rx) = oneshot::channel(); + let expected = glib::MainContext::ref_thread_default().block_on(async { + File::for_path(&my_temp_dir.path).eject_mountable_with_operation( + MountUnmountFlags::NONE, + MountOperation::NONE, + Cancellable::NONE, + move |res| tx.send(res).unwrap(), + ); + rx.await.unwrap() + }); + + // both results should equal + assert_eq!( + res.map_err(|err| err.kind::()), + expected.map_err(|err| err.kind::()) + ); + }); +} + +#[test] +fn file_poll_mountable() { + // run test in a main context dedicated and configured as the thread default one + let _ = glib::MainContext::new().with_thread_default(|| { + // temporary dir is deleted when the variable goes out of scope + let my_temp_dir = Temp::make_dir("poll_mountable_XXXXXX"); + + // invoke `MyLocalFile` implementation of `gio::ffi::GFileIface::poll_mountable` + let my_local_dir = glib::Object::builder::() + .property("path", &my_temp_dir.path) + .build(); + let (tx, rx) = oneshot::channel(); + let res = glib::MainContext::ref_thread_default().block_on(async { + my_local_dir.poll_mountable(Cancellable::NONE, move |res| tx.send(res).unwrap()); + rx.await.unwrap() + }); + + // invoke `LocalFile` implementation of `gio::ffi::GFileIface::poll_mountable` + let (tx, rx) = oneshot::channel(); + let expected = glib::MainContext::ref_thread_default().block_on(async { + File::for_path(&my_temp_dir.path) + .poll_mountable(Cancellable::NONE, move |res| tx.send(res).unwrap()); + rx.await.unwrap() + }); + + // both results should equal + assert_eq!( + res.map_err(|err| err.kind::()), + expected.map_err(|err| err.kind::()) + ); + }); +} + +#[test] +fn file_measure_disk_usage() { + // temporary dir is deleted when the variable goes out of scope + let my_temp_dir = Temp::make_dir("measure_disk_usage_XXXXXX"); + + // invoke `MyLocalFile` implementation of `gio::ffi::GFileIface::measure_disk_usage` + let my_local_dir = glib::Object::builder::() + .property("path", &my_temp_dir.path) + .build(); + let res = my_local_dir.measure_disk_usage(FileMeasureFlags::all(), Cancellable::NONE, None); + + // invoke `LocalFile` implementation of `gio::ffi::GFileIface::measure_disk_usage` + let expected = File::for_path(&my_temp_dir.path).measure_disk_usage( + FileMeasureFlags::all(), + Cancellable::NONE, + None, + ); + + // both results should equal + assert_eq!( + res.map_err(|err| err.kind::()), + expected.map_err(|err| err.kind::()) + ); +} diff --git a/gio/tests/file_utilities/mod.rs b/gio/tests/file_utilities/mod.rs index fac8b60e91b3..60c47f2c5ac0 100644 --- a/gio/tests/file_utilities/mod.rs +++ b/gio/tests/file_utilities/mod.rs @@ -1,8 +1,16 @@ // Take a look at the license at the top of the repository in the LICENSE file. use std::ops::Deref; +#[cfg(unix)] +use std::os::fd::{FromRawFd, OwnedFd}; +#[cfg(windows)] +use std::os::windows::prelude::{FromRawHandle, OwnedHandle, RawHandle}; -use gio::{prelude::*, Cancellable, File}; +#[cfg(unix)] +use gio::UnixOutputStream; +#[cfg(windows)] +use gio::Win32OutputStream; +use gio::{prelude::*, Cancellable, File, OutputStream}; use glib::translate::{FromGlibPtrFull, ToGlibPtr}; // Temp is a test utility that creates a new temporary file (or directory) and delete it at drop time. @@ -32,6 +40,37 @@ impl Temp { } } + // Create a new temporary file and return an auto closeable output stream to write in. + pub fn create_file(tmpl: &str) -> (Self, AutoCloseableOutpuStream) { + unsafe { + let mut name_used = std::ptr::null_mut(); + let mut error = std::ptr::null_mut(); + let fd = glib::ffi::g_file_open_tmp(tmpl.to_glib_none().0, &mut name_used, &mut error); + assert!(error.is_null(), "{}", glib::Error::from_glib_full(error)); + assert_ne!(fd, -1, "file not created"); + #[cfg(unix)] + let output_stream = UnixOutputStream::take_fd(OwnedFd::from_raw_fd(fd)).upcast(); + #[cfg(windows)] + let output_stream = Win32OutputStream::take_handle(OwnedHandle::from_raw_handle( + libc::get_osfhandle(fd) as RawHandle, + )) + .upcast(); + let path = glib::GString::from_glib_full(name_used).as_str().to_owned(); + let file = File::for_parse_name(&path); + let res = file.basename(); + assert!(res.is_some()); + let basename = res.unwrap().as_path().to_str().unwrap().to_owned(); + ( + Self { + file: Some(file), + path, + basename, + }, + AutoCloseableOutpuStream(output_stream), + ) + } + } + // Create a new temporary file under a temporary directory. pub fn create_file_child(&self, tmpl: &str) -> Self { unsafe { @@ -40,8 +79,10 @@ impl Temp { assert_ne!(fd, -1, "file not created"); { // close file - use std::os::fd::FromRawFd; + #[cfg(unix)] let _ = std::fs::File::from_raw_fd(fd); + #[cfg(windows)] + let _ = std::fs::File::from_raw_handle(libc::get_osfhandle(fd) as RawHandle); } let path = tmpl.as_str().to_owned(); let file = File::for_parse_name(&path); @@ -82,3 +123,23 @@ impl Drop for Temp { } } } + +// AutoCloseableOutpuStream is a test utility that takes ownership of an output stream and close it at drop time. +pub struct AutoCloseableOutpuStream(OutputStream); + +impl Deref for AutoCloseableOutpuStream { + type Target = OutputStream; + + // Dereference self to the inner output stream. + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Drop for AutoCloseableOutpuStream { + // Close the inner output stream. + fn drop(&mut self) { + let res = self.0.close(Cancellable::NONE); + assert!(res.is_ok(), "{}", res.err().unwrap()); + } +} From faa3f819c0a9c89de58d3655d3b0dde90f52471e Mon Sep 17 00:00:00 2001 From: fbrouille Date: Wed, 21 May 2025 08:15:06 +0000 Subject: [PATCH 3/4] Add gio::Vfs subclass Signed-off-by: fbrouille --- gio/Gir.toml | 5 ++ gio/src/subclass/mod.rs | 2 + gio/src/subclass/vfs.rs | 184 ++++++++++++++++++++++++++++++++++++++++ gio/tests/vfs.rs | 138 ++++++++++++++++++++++++++++++ 4 files changed, 329 insertions(+) create mode 100644 gio/src/subclass/vfs.rs create mode 100644 gio/tests/vfs.rs diff --git a/gio/Gir.toml b/gio/Gir.toml index dfff62e9521c..227f6f5f0b0b 100644 --- a/gio/Gir.toml +++ b/gio/Gir.toml @@ -1740,6 +1740,11 @@ cfg_condition = "unix" name = "Gio.Vfs" status = "generate" concurrency = "send+sync" + [[object.function]] + name = "get_file_for_path" + [[object.function.parameter]] + name = "path" + string_type = "filename" [[object]] name = "Gio.Volume" diff --git a/gio/src/subclass/mod.rs b/gio/src/subclass/mod.rs index 85d8ba5dc68e..a99a96f21c54 100644 --- a/gio/src/subclass/mod.rs +++ b/gio/src/subclass/mod.rs @@ -14,6 +14,7 @@ mod list_model; mod output_stream; mod seekable; mod socket_control_message; +mod vfs; pub use self::application::ArgumentList; @@ -36,5 +37,6 @@ pub mod prelude { output_stream::{OutputStreamImpl, OutputStreamImplExt}, seekable::{SeekableImpl, SeekableImplExt}, socket_control_message::{SocketControlMessageImpl, SocketControlMessageImplExt}, + vfs::{VfsImpl, VfsImplExt}, }; } diff --git a/gio/src/subclass/vfs.rs b/gio/src/subclass/vfs.rs new file mode 100644 index 000000000000..704d50896798 --- /dev/null +++ b/gio/src/subclass/vfs.rs @@ -0,0 +1,184 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use glib::{prelude::*, subclass::prelude::*, translate::*, GString}; + +use libc::c_char; + +use crate::{ffi, File, Vfs}; + +// Support custom implementation of virtual functions defined in `gio::ffi::GVfsClass`. +pub trait VfsImpl: ObjectImpl + ObjectSubclass> { + fn is_active(&self) -> bool { + self.parent_is_active() + } + + fn get_file_for_path(&self, path: impl AsRef) -> File { + self.parent_get_file_for_path(path) + } + + fn get_file_for_uri(&self, uri: &str) -> File { + self.parent_get_file_for_uri(uri) + } + + fn get_supported_uri_schemes(&self) -> Vec { + self.parent_get_supported_uri_schemes() + } + + fn parse_name(&self, parse_name: &str) -> File { + self.parent_parse_name(parse_name) + } +} + +// Support parent implementation of virtual functions defined in `gio::ffi::GVfsClass`. +pub trait VfsImplExt: VfsImpl { + fn parent_is_active(&self) -> bool { + unsafe { + let data = Self::type_data(); + let parent_class = data.as_ref().parent_class() as *const ffi::GVfsClass; + + let f = (*parent_class) + .is_active + .expect("No parent class implementation for \"is_active\""); + + let res = f(self.obj().unsafe_cast_ref::().to_glib_none().0); + from_glib(res) + } + } + + fn parent_get_file_for_path(&self, path: impl AsRef) -> File { + unsafe { + let data = Self::type_data(); + let parent_class = data.as_ref().parent_class() as *const ffi::GVfsClass; + + let f = (*parent_class) + .get_file_for_path + .expect("No parent class implementation for \"get_file_for_path\""); + + let res = f( + self.obj().unsafe_cast_ref::().to_glib_none().0, + path.as_ref().to_glib_none().0, + ); + from_glib_full(res) + } + } + + fn parent_get_file_for_uri(&self, uri: &str) -> File { + unsafe { + let data = Self::type_data(); + let parent_class = data.as_ref().parent_class() as *const ffi::GVfsClass; + + let f = (*parent_class) + .get_file_for_uri + .expect("No parent class implementation for \"get_file_for_uri\""); + + let res = f( + self.obj().unsafe_cast_ref::().to_glib_none().0, + uri.to_glib_none().0, + ); + from_glib_full(res) + } + } + + fn parent_get_supported_uri_schemes(&self) -> Vec { + unsafe { + let data = Self::type_data(); + let parent_class = data.as_ref().parent_class() as *const ffi::GVfsClass; + + let f = (*parent_class) + .get_supported_uri_schemes + .expect("No parent class implementation for \"get_supported_uri_schemes\""); + + let res = f(self.obj().unsafe_cast_ref::().to_glib_none().0); + FromGlibPtrContainer::from_glib_none(res) + } + } + + fn parent_parse_name(&self, parse_name: &str) -> File { + unsafe { + let data = Self::type_data(); + let parent_class = data.as_ref().parent_class() as *const ffi::GVfsClass; + + let f = (*parent_class) + .parse_name + .expect("No parent class implementation for \"parse_name\""); + + let res = f( + self.obj().unsafe_cast_ref::().to_glib_none().0, + parse_name.to_glib_none().0, + ); + from_glib_full(res) + } + } +} + +impl VfsImplExt for T {} + +// Implement virtual functions defined in `gio::ffi::GVfsClass`. +unsafe impl IsSubclassable for Vfs { + fn class_init(class: &mut ::glib::Class) { + Self::parent_class_init::(class); + + let klass = class.as_mut(); + klass.is_active = Some(is_active::); + klass.get_file_for_path = Some(get_file_for_path::); + klass.get_file_for_uri = Some(get_file_for_uri::); + klass.get_supported_uri_schemes = Some(get_supported_uri_schemes::); + klass.parse_name = Some(parse_name::); + } +} + +unsafe extern "C" fn is_active(vfs: *mut ffi::GVfs) -> glib::ffi::gboolean { + let instance = &*(vfs as *mut T::Instance); + let imp = instance.imp(); + + let res = imp.is_active(); + + res.into_glib() +} + +unsafe extern "C" fn get_file_for_path( + vfs: *mut ffi::GVfs, + path: *const c_char, +) -> *mut ffi::GFile { + let instance = &*(vfs as *mut T::Instance); + let imp = instance.imp(); + + let file = imp.get_file_for_path(GString::from_glib_borrow(path).as_ref()); + + file.to_glib_full() +} + +unsafe extern "C" fn get_file_for_uri( + vfs: *mut ffi::GVfs, + uri: *const c_char, +) -> *mut ffi::GFile { + let instance = &*(vfs as *mut T::Instance); + let imp = instance.imp(); + + let file = imp.get_file_for_uri(&GString::from_glib_borrow(uri)); + + file.to_glib_full() +} + +unsafe extern "C" fn get_supported_uri_schemes( + vfs: *mut ffi::GVfs, +) -> *const *const c_char { + let instance = &*(vfs as *mut T::Instance); + let imp = instance.imp(); + + let supported_uri_schemes = imp.get_supported_uri_schemes(); + + supported_uri_schemes.to_glib_full() +} + +unsafe extern "C" fn parse_name( + vfs: *mut ffi::GVfs, + parse_name: *const c_char, +) -> *mut ffi::GFile { + let instance = &*(vfs as *mut T::Instance); + let imp = instance.imp(); + + let file = imp.parse_name(&GString::from_glib_borrow(parse_name)); + + file.to_glib_full() +} diff --git a/gio/tests/vfs.rs b/gio/tests/vfs.rs new file mode 100644 index 000000000000..ac593f0e6f5a --- /dev/null +++ b/gio/tests/vfs.rs @@ -0,0 +1,138 @@ +// Take a look at the license at the top of the repository in the LICENSE file. +// +// The following tests rely on a custom type `MyLocalVfs` that extends the existing GIO type `GLocalVfs`. +// For each virtual method defined in class `gio::ffi::GVfsClass`, a test checks that `MyLocalVfs` and `GLocalVfs` return the same results. +// Note that a `MyLocalVfs` instance is built explicitly by calling `glib::Object::builder` whereas a a `GLocalVfs` instance is created by calling `gio::auto::Vfs::local`. + +use gio::{prelude::*, subclass::prelude::*, File, Vfs}; +use glib::translate::ToGlibPtr; + +// Binding of existing GIO type GLocalVfs. +mod ffi { + use gio::ffi; + + #[derive(Copy, Clone)] + #[repr(C)] + pub struct GLocalVfs { + pub parent_instance: ffi::GVfs, + } + + #[derive(Copy, Clone)] + #[repr(C)] + pub struct GLocalVfsClass { + pub parent_class: ffi::GVfsClass, + } +} + +glib::wrapper! { + #[doc(alias = "GLocalVfs")] + pub struct LocalVfs(Object) @extends Vfs; + + match fn { + type_ => || { + use std::sync::Once; + static ONCE: Once = Once::new(); + + // ensure type is initialized by calling `gio::auto::File::for_path` to create a `GLocalFile` instance. + ONCE.call_once(|| unsafe { + let _ = File::for_path("path"); + }); + glib::gobject_ffi::g_type_from_name("GLocalVfs".to_glib_none().0) + }, + } +} + +pub trait LocalVfsImpl: ObjectImpl + ObjectSubclass + IsA> {} + +unsafe impl IsSubclassable for LocalVfs {} + +// Define `MyLocalVfs` as a subclass of `GLocalVfs`. +mod imp { + use super::*; + + #[derive(Default)] + pub struct MyLocalVfs; + + #[glib::object_subclass] + impl ObjectSubclass for MyLocalVfs { + const NAME: &'static str = "MyLocalVfs"; + type Type = super::MyLocalVfs; + type ParentType = LocalVfs; + } + + impl ObjectImpl for MyLocalVfs {} + + // Implements `VfsImpl` with default implementation, which calls the parent's implementation. + impl VfsImpl for MyLocalVfs {} + + impl LocalVfsImpl for MyLocalVfs {} +} + +glib::wrapper! { + pub struct MyLocalVfs(ObjectSubclass) @extends LocalVfs, Vfs; +} + +#[test] +fn vfs_is_active() { + // invoke `MyLocalVfs` implementation of `gio::ffi::GVfsClass::is_active` + let my_local_vfs = glib::Object::new::(); + let active = my_local_vfs.is_active(); + + // invoke `LocalVfs` implementation of `gio::ffi::GVfsClass::is_active` + let expected = Vfs::local().is_active(); + + // both results should equal + assert_eq!(active, expected); +} + +#[test] +fn vfs_get_file_for_path() { + // invoke `MyLocalVfs` implementation of `gio::ffi::GVfsClass::get_file_for_path` + let my_local_vfs = glib::Object::new::(); + let file = my_local_vfs.file_for_path("/path"); + + // invoke `LocalVfs` implementation of `gio::ffi::GVfsClass::get_file_for_path` + let expected = Vfs::local().file_for_path("/path"); + + // both files should equal + assert!(file.equal(&expected)); +} + +#[test] +fn vfs_get_file_for_uri() { + // invoke `MyLocalVfs` implementation of `gio::ffi::GVfsClass::get_file_for_uri` + let my_local_vfs = glib::Object::new::(); + let file = my_local_vfs.file_for_uri("file:///path"); + + // invoke `LocalVfs` implementation of `gio::ffi::GVfsClass::get_file_for_uri` + let expected = Vfs::local().file_for_uri("file:///path"); + + // both files should equal + assert!(file.equal(&expected)); +} + +#[test] +fn vfs_get_supported_uri_schemes() { + // invoke `MyLocalVfs` implementation of `gio::ffi::GVfsClass::supported_uri_schemes` + let my_local_vfs = glib::Object::new::(); + let schemes = my_local_vfs.supported_uri_schemes(); + + // invoke `LocalVfs` implementation of `gio::ffi::GVfsClass::supported_uri_schemes` + let expected = Vfs::local().supported_uri_schemes(); + + // both results should equal + assert_eq!(schemes, expected); +} + +#[test] +fn vfs_parse_name() { + // invoke `MyLocalVfs` implementation of `gio::ffi::GVfsClass::parse_name` + let my_local_vfs = glib::Object::new::(); + let file = my_local_vfs.parse_name("file:///path"); + + // invoke `LocalVfs` implementation of `gio::ffi::GVfsClass::parse_name` + let expected = Vfs::local().parse_name("file:///path"); + + // both files should equal + assert!(file.equal(&expected)); +} From 72eca839fa9d588ec7673e604e8c9b7d808deeb1 Mon Sep 17 00:00:00 2001 From: fbrouille Date: Thu, 29 May 2025 15:41:52 +0000 Subject: [PATCH 4/4] Add an example for gio::Vfs implementation Signed-off-by: fbrouille --- Cargo.lock | 12 +- examples/Cargo.toml | 5 + examples/gio_vfs/README.md | 54 +++ examples/gio_vfs/file.rs | 533 ++++++++++++++++++++++++++++ examples/gio_vfs/file_enumerator.rs | 94 +++++ examples/gio_vfs/file_monitor.rs | 121 +++++++ examples/gio_vfs/lib.rs | 144 ++++++++ examples/gio_vfs/vfs.rs | 87 +++++ 8 files changed, 1044 insertions(+), 6 deletions(-) create mode 100644 examples/gio_vfs/README.md create mode 100644 examples/gio_vfs/file.rs create mode 100644 examples/gio_vfs/file_enumerator.rs create mode 100644 examples/gio_vfs/file_monitor.rs create mode 100644 examples/gio_vfs/lib.rs create mode 100644 examples/gio_vfs/vfs.rs diff --git a/Cargo.lock b/Cargo.lock index 3b77c040485a..169ae313946f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1232,9 +1232,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.20" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" +checksum = "900f6c86a685850b1bc9f6223b20125115ee3f31e01207d81655bbcc0aea9231" dependencies = [ "serde", "serde_spanned", @@ -1244,18 +1244,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.24" +version = "0.22.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" +checksum = "10558ed0bd2a1562e630926a2d1f0b98c827da99fabd3fe20920a59642504485" dependencies = [ "indexmap", "serde", diff --git a/examples/Cargo.toml b/examples/Cargo.toml index c703b6bf524f..b1c990a3285d 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -51,3 +51,8 @@ path = "object_subclass/main.rs" [[bin]] name = "virtual_methods" path = "virtual_methods/main.rs" + +[lib] +name = "gio_vfs" +path = "gio_vfs/lib.rs" +crate-type = ["cdylib"] diff --git a/examples/gio_vfs/README.md b/examples/gio_vfs/README.md new file mode 100644 index 000000000000..1b0f2da24ac0 --- /dev/null +++ b/examples/gio_vfs/README.md @@ -0,0 +1,54 @@ +# GIO VFS + +This example demonstrates the usage of GIO VFS. Built artifact is a dynamic system library that is used as a GIO module +(see https://docs.gtk.org/gio/overview.html#running-gio-applications) +and that implement support of file operations for files with uri starting with `myvfs:///` + +Build, install and configure it by executing: +```bash +cargo build -p gtk-rs-examples --lib +export GIO_EXTRA_MODULES=/tmp/gio_modules +mkdir -p $GIO_EXTRA_MODULES && cp ./target/debug/libgio_vfs.so $GIO_EXTRA_MODULES +export MYVFS_ROOT=/tmp/myvfs +mkdir -p $MYVFS_ROOT +``` + +`GIO_EXTRA_MODULES` specify additional directories for `gio` command line tool to automatically load modules. + +`MYVFS_ROOT` specify the local directory that is used by as backend directory for uri starting with `myvfs:///` (e.g. if `MYVFS_ROOT-/tmp` `myvfs:///foo` points to `/tmp/foo`). + +`gio` commandline tool (see https://gnome.pages.gitlab.gnome.org/libsoup/gio/gio.html) automatically loads this extra module. + +Run it by executing the following commands: + +Basic operations: +```bash +echo "foo" | gio save myvfs:///foo +gio cat myvfs:///foo +gio set -t string myvfs:///foo xattr::my_string value +gio info myvfs:///foo +gio mkdir myvfs:///bar +gio copy myvfs:///foo myvfs:///bar/ +gio list myvfs:/// +gio tree myvfs:/// +gio move -b myvfs:///bar/foo myvfs:///foo +gio tree myvfs:/// +gio remove myvfs:///foo myvfs:///foo~ myvfs:///bar +gio list myvfs:/// +``` + +Monitor `myvfs:///`: +```bash +# monitor is a blocking operation. kill it with Ctrl+C +gio monitor myvfs:/// +``` + +```bash +# in another terminal (ensure MYVFS_ROOT is defined) +touch $MYVFS_ROOT/foo +echo "foo" > $MYVFS_ROOT/foo +mkdir $MYVFS_ROOT/bar +cp $MYVFS_ROOT/foo $MYVFS_ROOT/foo2 +mv -b $MYVFS_ROOT/foo2 $MYVFS_ROOT/foo +rm -rf $MYVFS_ROOT/* +``` diff --git a/examples/gio_vfs/file.rs b/examples/gio_vfs/file.rs new file mode 100644 index 000000000000..e39117def586 --- /dev/null +++ b/examples/gio_vfs/file.rs @@ -0,0 +1,533 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use std::path::PathBuf; + +use gio::prelude::*; +use gio::subclass::prelude::*; +use gio::File; +use gio::Vfs; +use glib::g_debug; +use glib::Object; + +// Define `MyFile` as an implementation of `File`. +pub mod imp { + use std::{path::PathBuf, sync::OnceLock}; + + use gio::{ + Cancellable, FileAttributeInfoList, FileAttributeValue, FileCopyFlags, FileCreateFlags, + FileEnumerator, FileInfo, FileInputStream, FileMonitor, FileMonitorFlags, FileOutputStream, + FileQueryInfoFlags, IOErrorEnum, + }; + use glib::{translate::ToGlibPtr, Error, Properties}; + + use crate::{ + file_enumerator::MyFileEnumerator, + file_monitor::MyFileMonitor, + {update_file_info, SCHEME}, + }; + + use super::*; + + #[derive(Properties, Default, Debug)] + #[properties(wrapper_type = super::MyFile)] + pub struct MyFile { + #[property(get, set)] + virtual_path: OnceLock, + #[property(get, set)] + local_file: OnceLock, + } + + impl MyFile { + fn virtual_path(&self) -> &PathBuf { + self.virtual_path.get().unwrap() + } + + fn local_file(&self) -> &File { + self.local_file.get().unwrap() + } + } + + #[glib::object_subclass] + #[object_subclass_dynamic(lazy_registration = true)] + impl ObjectSubclass for MyFile { + const NAME: &'static str = "MyFile"; + type Type = super::MyFile; + type Interfaces = (File,); + } + + #[glib::derived_properties] + impl ObjectImpl for MyFile {} + + impl FileImpl for MyFile { + fn dup(&self) -> File { + g_debug!("MyVfs", "MyFile::dup({:?})", self); + Self::Type::new(self.virtual_path().clone(), self.local_file().clone()).upcast() + } + + fn hash(&self) -> u32 { + g_debug!("MyVfs", "MyFile::hash({:?})", self); + unsafe { + gio::ffi::g_file_hash( + ToGlibPtr::<*const gio::ffi::GFile>::to_glib_none(self.local_file()).0 + as *const _, + ) + } + } + + fn equal(&self, file2: &File) -> bool { + g_debug!("MyVfs", "MyFile::equal({:?},{:?})", self, file2); + match file2.downcast_ref::() { + Some(file2) => self.local_file().equal(file2), + None => false, + } + } + + fn is_native(&self) -> bool { + g_debug!("MyVfs", "MyFile::is_native({:?})", self); + false + } + + fn has_uri_scheme(&self, uri_scheme: &str) -> bool { + g_debug!("MyVfs", "MyFile::has_uri_scheme({:?},{})", self, uri_scheme); + uri_scheme == SCHEME + } + + fn uri_scheme(&self) -> Option { + g_debug!("MyVfs", "MyFile::uri_scheme({:?})", self); + Some(SCHEME.to_owned()) + } + + fn basename(&self) -> Option { + g_debug!("MyVfs", "MyFile::basename({:?})", self); + self.local_file().basename() + } + + fn path(&self) -> Option { + g_debug!("MyVfs", "MyFile::path({:?})", self); + self.local_file().path() + } + + fn uri(&self) -> String { + g_debug!("MyVfs", "MyFile::uri({:?})", self); + format!( + "{}://{}", + SCHEME, + self.local_file().path().unwrap().to_string_lossy() + ) + } + + fn parse_name(&self) -> String { + g_debug!("MyVfs", "MyFile::parse_name({:?})", self); + self.uri() + } + + fn parent(&self) -> Option { + g_debug!("MyVfs", "MyFile::parent({:?})", self); + match (self.virtual_path().parent(), self.local_file().parent()) { + (Some(virtual_path), Some(local_file)) => { + Some(Self::Type::new(virtual_path.to_path_buf(), local_file).upcast()) + } + _ => None, + } + } + + fn has_prefix(&self, prefix: &File) -> bool { + g_debug!("MyVfs", "MyFile::has_prefix({:?},{:?})", self, prefix); + self.local_file().has_prefix(prefix) + } + + fn relative_path(&self, descendant: &File) -> Option { + g_debug!( + "MyVfs", + "MyFile::relative_path({:?},{:?})", + self, + descendant + ); + match descendant.downcast_ref::() { + Some(descendant) => descendant + .virtual_path() + .strip_prefix(self.virtual_path()) + .ok() + .map(PathBuf::from), + None => None, + } + } + + fn resolve_relative_path(&self, relative_path: impl AsRef) -> File { + g_debug!( + "MyVfs", + "MyFile::resolve_relative_path({:?},{:?})", + self, + relative_path.as_ref() + ); + let relative_path_as_pb = PathBuf::from(relative_path.as_ref()); + let (virtual_path, local_file) = if relative_path_as_pb.is_absolute() { + ( + relative_path_as_pb, + Vfs::local().file_for_path(relative_path), + ) + } else { + ( + self.virtual_path().join(relative_path_as_pb), + self.local_file().resolve_relative_path(relative_path), + ) + }; + Self::Type::new(virtual_path, local_file).upcast() + } + + fn child_for_display_name(&self, display_name: &str) -> Result { + g_debug!( + "MyVfs", + "MyFile::child_for_display_name({:?},{})", + self, + display_name + ); + let virtual_path = self.virtual_path().join(display_name); + let local_file = self.local_file().child_for_display_name(display_name)?; + Ok(Self::Type::new(virtual_path, local_file).upcast()) + } + + fn enumerate_children( + &self, + attributes: &str, + flags: FileQueryInfoFlags, + cancellable: Option<&impl IsA>, + ) -> Result { + g_debug!( + "MyVfs", + "MyFile::enumerate_children({:?},{},{:?},{:?})", + self, + attributes, + flags, + cancellable.map(|_| "_") + ); + MyFileEnumerator::new(self.local_file(), attributes, flags, cancellable) + .map(|my_file_enumerator| my_file_enumerator.upcast()) + } + + fn query_info( + &self, + attributes: &str, + flags: FileQueryInfoFlags, + cancellable: Option<&impl IsA>, + ) -> Result { + g_debug!( + "MyVfs", + "MyFile::query_info({:?},{},{:?},{:?})", + self, + attributes, + flags, + cancellable.map(|_| "_") + ); + let info = self + .local_file() + .query_info(attributes, flags, cancellable)?; + update_file_info(&info); + Ok(info) + } + + fn query_filesystem_info( + &self, + attributes: &str, + cancellable: Option<&impl IsA>, + ) -> Result { + g_debug!( + "MyVfs", + "MyFile::query_filesystem_info({:?},{},{:?})", + self, + attributes, + cancellable.map(|_| "_") + ); + let info = self + .local_file() + .query_filesystem_info(attributes, cancellable)?; + update_file_info(&info); + Ok(info) + } + + fn set_display_name( + &self, + display_name: &str, + cancellable: Option<&impl IsA>, + ) -> Result { + g_debug!( + "MyVfs", + "MyFile::set_display_name({:?},{},{:?})", + self, + display_name, + cancellable.map(|_| "_") + ); + let mut virtual_path = self.virtual_path().clone(); + let local_file = self + .local_file() + .set_display_name(display_name, cancellable)?; + let basename = local_file.basename().ok_or(Error::new( + IOErrorEnum::InvalidFilename, + &format!( + "failed to rename {} to {}", + virtual_path.file_name().unwrap().to_string_lossy(), + display_name + ), + ))?; + virtual_path.set_file_name(basename); + Ok(Self::Type::new(virtual_path, local_file).upcast()) + } + + fn query_settable_attributes( + &self, + cancellable: Option<&impl IsA>, + ) -> Result { + g_debug!( + "MyVfs", + "MyFile::query_settable_attributes({:?},{:?})", + self, + cancellable.map(|_| "_") + ); + self.local_file().query_settable_attributes(cancellable) + } + + fn query_writable_namespaces( + &self, + cancellable: Option<&impl IsA>, + ) -> Result { + g_debug!( + "MyVfs", + "MyFile::query_writable_namespaces({:?},{:?})", + self, + cancellable.map(|_| "_") + ); + self.local_file().query_writable_namespaces(cancellable) + } + + fn set_attribute<'a>( + &self, + attribute: &str, + value: impl Into>, + flags: FileQueryInfoFlags, + cancellable: Option<&impl IsA>, + ) -> Result<(), Error> { + let value: FileAttributeValue<'a> = value.into(); + g_debug!( + "MyVfs", + "MyFile::set_attribute({:?},{},{:?},{:?},{:?})", + self, + attribute, + value, + flags, + cancellable.map(|_| "_") + ); + self.local_file() + .set_attribute(attribute, value, flags, cancellable) + } + + fn read_fn( + &self, + cancellable: Option<&impl IsA>, + ) -> Result { + g_debug!( + "MyVfs", + "MyFile::read_fn({:?},{:?})", + self, + cancellable.map(|_| "_") + ); + self.local_file().read(cancellable) + } + + fn append_to( + &self, + flags: FileCreateFlags, + cancellable: Option<&impl IsA>, + ) -> Result { + g_debug!( + "MyVfs", + "MyFile::append_to({:?},{:?},{:?})", + self, + flags, + cancellable.map(|_| "_") + ); + self.local_file().append_to(flags, cancellable) + } + + fn create( + &self, + flags: FileCreateFlags, + cancellable: Option<&impl IsA>, + ) -> Result { + g_debug!( + "MyVfs", + "MyFile::create({:?},{:?},{:?})", + self, + flags, + cancellable.map(|_| "_") + ); + self.local_file().create(flags, cancellable) + } + + fn replace( + &self, + etag: Option<&str>, + make_backup: bool, + flags: FileCreateFlags, + cancellable: Option<&impl IsA>, + ) -> Result { + g_debug!( + "MyVfs", + "MyFile::replace({:?},{:?},{},{:?},{:?})", + self, + etag, + make_backup, + flags, + cancellable.map(|_| "_") + ); + self.local_file().replace( + etag.map(AsRef::::as_ref), + make_backup, + flags, + cancellable, + ) + } + + fn delete(&self, cancellable: Option<&impl IsA>) -> Result<(), Error> { + g_debug!( + "MyVfs", + "MyFile::delete({:?},{:?})", + self, + cancellable.map(|_| "_") + ); + self.local_file().delete(cancellable) + } + + fn trash(&self, cancellable: Option<&impl IsA>) -> Result<(), Error> { + g_debug!( + "MyVfs", + "MyFile::trash({:?},{:?})", + self, + cancellable.map(|_| "_") + ); + self.local_file().trash(cancellable) + } + + fn make_directory(&self, cancellable: Option<&impl IsA>) -> Result<(), Error> { + g_debug!( + "MyVfs", + "MyFile::make_directory({:?},{:?})", + self, + cancellable.map(|_| "_") + ); + self.local_file().make_directory(cancellable) + } + + fn copy( + source: &File, + destination: &File, + flags: FileCopyFlags, + cancellable: Option<&impl IsA>, + progress_callback: Option<&mut dyn (FnMut(i64, i64))>, + ) -> Result<(), Error> { + g_debug!( + "MyVfs", + "MyFile::copy({:?},{:?},{:?},{:?},{:?})", + source, + destination, + flags, + cancellable.map(|_| "_"), + progress_callback.as_ref().map(|_| "_") + ); + let source = source + .downcast_ref::() + .map(|my_file| my_file.imp().local_file()) + .unwrap_or(source); + let destination = destination + .downcast_ref::() + .map(|my_file| my_file.imp().local_file()) + .unwrap_or(destination); + source.copy(destination, flags, cancellable, progress_callback) + } + + fn move_( + source: &File, + destination: &File, + flags: FileCopyFlags, + cancellable: Option<&impl IsA>, + progress_callback: Option<&mut dyn (FnMut(i64, i64))>, + ) -> Result<(), Error> { + g_debug!( + "MyVfs", + "MyFile::move_({:?},{:?},{:?},{:?},{:?})", + source, + destination, + flags, + cancellable.map(|_| "_"), + progress_callback.as_ref().map(|_| "_") + ); + let source = source + .downcast_ref::() + .map(|my_file| my_file.imp().local_file()) + .unwrap_or(source); + let destination = destination + .downcast_ref::() + .map(|my_file| my_file.imp().local_file()) + .unwrap_or(destination); + source.move_(destination, flags, cancellable, progress_callback) + } + + fn monitor_dir( + &self, + flags: FileMonitorFlags, + cancellable: Option<&impl IsA>, + ) -> Result { + g_debug!( + "MyVfs", + "MyFile::monitor_dir({:?},{:?},{:?})", + self, + flags, + cancellable.map(|_| "_") + ); + MyFileMonitor::for_directory(self.local_file(), flags, cancellable) + .map(|my_file_monitor| my_file_monitor.upcast()) + } + + fn monitor_file( + &self, + flags: FileMonitorFlags, + cancellable: Option<&impl IsA>, + ) -> Result { + g_debug!( + "MyVfs", + "MyFile::monitor_file({:?},{:?},{:?})", + self, + flags, + cancellable.map(|_| "_") + ); + MyFileMonitor::for_file(self.local_file(), flags, cancellable) + .map(|my_file_monitor| my_file_monitor.upcast()) + } + } +} + +glib::wrapper! { + pub struct MyFile(ObjectSubclass) @implements File; +} + +impl MyFile { + pub fn new(virtual_path: PathBuf, local_file: File) -> Self { + g_debug!("MyVfs", "MyFile::new({:?},{:?})", virtual_path, local_file); + Object::builder() + .property("virtual_path", virtual_path) + .property("local_file", local_file) + .build() + } +} + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + + use super::*; + + #[test] + fn test() { + let f = MyFile::new(PathBuf::from("/"), Vfs::local().file_for_path("/")); + assert_eq!(f.path(), Some(PathBuf::from("/"))); + } +} diff --git a/examples/gio_vfs/file_enumerator.rs b/examples/gio_vfs/file_enumerator.rs new file mode 100644 index 000000000000..6fd6442c4bab --- /dev/null +++ b/examples/gio_vfs/file_enumerator.rs @@ -0,0 +1,94 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use gio::prelude::*; +use gio::subclass::prelude::*; +use gio::Cancellable; +use gio::File; +use gio::FileEnumerator; +use gio::FileQueryInfoFlags; +use glib::g_debug; +use glib::Error; +use glib::Object; + +// Define `MyFileEnumerator` as an implementation of `FileEnumerator`. +pub mod imp { + use std::{path::PathBuf, sync::OnceLock}; + + use glib::Properties; + + use crate::update_file_info; + + use super::*; + + // #[derive(Default)] + #[derive(Properties, Default)] + #[properties(wrapper_type = super::MyFileEnumerator)] + pub struct MyFileEnumerator { + #[property(get, set)] + virtual_path: OnceLock, + #[property(get, set)] + local_file_enumerator: OnceLock, + } + + impl MyFileEnumerator { + fn local_file_enumerator(&self) -> &FileEnumerator { + self.local_file_enumerator.get().unwrap() + } + } + + #[glib::object_subclass] + #[object_subclass_dynamic(lazy_registration = true)] + impl ObjectSubclass for MyFileEnumerator { + const NAME: &'static str = "MyFileEnumerator"; + type Type = super::MyFileEnumerator; + type ParentType = FileEnumerator; + } + + #[glib::derived_properties] + impl ObjectImpl for MyFileEnumerator {} + + impl FileEnumeratorImpl for MyFileEnumerator { + fn next_file( + &self, + cancellable: Option<&gio::Cancellable>, + ) -> Result, glib::Error> { + if let Some(info) = self.local_file_enumerator().next_file(cancellable)? { + update_file_info(&info); + Ok(Some(info)) + } else { + Ok(None) + } + } + + fn close(&self, cancellable: Option<&gio::Cancellable>) -> Result<(), glib::Error> { + self.local_file_enumerator().close(cancellable) + } + } +} + +glib::wrapper! { + pub struct MyFileEnumerator(ObjectSubclass) @extends FileEnumerator; +} + +impl MyFileEnumerator { + pub fn new( + local_file: &File, + attributes: &str, + flags: FileQueryInfoFlags, + cancellable: Option<&impl IsA>, + ) -> Result { + g_debug!( + "MyVfs", + "MyFileEnumerator::new({:?},{},{:?},{:?})", + local_file, + attributes, + flags, + cancellable.map(|_| "_") + ); + let local_file_enumerator = + local_file.enumerate_children(attributes, flags, cancellable)?; + Ok(Object::builder() + .property("local_file_enumerator", local_file_enumerator) + .build()) + } +} diff --git a/examples/gio_vfs/file_monitor.rs b/examples/gio_vfs/file_monitor.rs new file mode 100644 index 000000000000..5850242d2640 --- /dev/null +++ b/examples/gio_vfs/file_monitor.rs @@ -0,0 +1,121 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use gio::prelude::*; +use gio::subclass::prelude::*; +use gio::Cancellable; +use gio::File; +use gio::FileMonitor; +use gio::FileMonitorFlags; +use glib::g_debug; +use glib::Error; +use glib::Object; + +use crate::file::MyFile; +use crate::resolve_local_path; + +// Define `MyFileMonitor` as an implementation of `FileMonitor`. +pub mod imp { + use std::{path::PathBuf, sync::OnceLock}; + + use glib::Properties; + + use super::*; + + // #[derive(Default)] + #[derive(Properties, Default)] + #[properties(wrapper_type = super::MyFileMonitor)] + pub struct MyFileMonitor { + #[property(get, set)] + virtual_path: OnceLock, + #[property(get, set)] + local_file_monitor: OnceLock, + } + + impl MyFileMonitor { + pub(super) fn local_file_monitor(&self) -> &FileMonitor { + self.local_file_monitor.get().unwrap() + } + } + + #[glib::object_subclass] + #[object_subclass_dynamic(lazy_registration = true)] + impl ObjectSubclass for MyFileMonitor { + const NAME: &'static str = "MyFileMonitor"; + type Type = super::MyFileMonitor; + type ParentType = FileMonitor; + } + + #[glib::derived_properties] + impl ObjectImpl for MyFileMonitor {} + + impl FileMonitorImpl for MyFileMonitor { + fn cancel(&self) { + self.local_file_monitor().cancel(); + } + } +} + +glib::wrapper! { + pub struct MyFileMonitor(ObjectSubclass) @extends FileMonitor; +} + +impl MyFileMonitor { + pub fn for_directory( + local_file: &File, + flags: FileMonitorFlags, + cancellable: Option<&impl IsA>, + ) -> Result { + g_debug!( + "MyVfs", + "MyFileMonitor::for_directory({:?},{:?},{:?})", + local_file, + flags, + cancellable.map(|_| "_") + ); + let local_file_monitor = local_file.monitor_directory(flags, cancellable)?; + Self::new(local_file_monitor) + } + + pub fn for_file( + local_file: &File, + flags: FileMonitorFlags, + cancellable: Option<&impl IsA>, + ) -> Result { + g_debug!( + "MyVfs", + "MyFileMonitor::for_file({:?},{:?},{:?})", + local_file, + flags, + cancellable.map(|_| "_") + ); + let local_file_monitor = local_file.monitor_file(flags, cancellable)?; + Self::new(local_file_monitor) + } + + pub fn new(local_file_monitor: FileMonitor) -> Result { + let file_monitor = Object::builder::() + .property("local_file_monitor", local_file_monitor) + .build(); + file_monitor + .imp() + .local_file_monitor() + .connect_changed(glib::clone!( + #[weak] + file_monitor, + move |_, local_file, other_local_file, event_type| { + let file = MyFile::new( + resolve_local_path(local_file.path().unwrap().to_string_lossy()).into(), + local_file.clone(), + ); + let other_file = other_local_file.map(|local_file| { + MyFile::new( + resolve_local_path(local_file.path().unwrap().to_string_lossy()).into(), + local_file.clone(), + ) + }); + file_monitor.emit_event(&file, other_file.as_ref(), event_type); + } + )); + Ok(file_monitor) + } +} diff --git a/examples/gio_vfs/lib.rs b/examples/gio_vfs/lib.rs new file mode 100644 index 000000000000..2176c2435346 --- /dev/null +++ b/examples/gio_vfs/lib.rs @@ -0,0 +1,144 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use gio::{IOErrorEnum, IOExtensionPoint, VFS_EXTENSION_POINT_NAME}; +use glib::{g_debug, translate::FromGlibPtrBorrow, types::StaticType}; + +mod file; +mod file_enumerator; +mod file_monitor; +mod vfs; + +#[no_mangle] +pub extern "C" fn g_io_module_load(module_ptr: *mut gio::ffi::GIOModule) { + let type_module = unsafe { glib::TypeModule::from_glib_borrow(module_ptr as *mut _) }; + let res = register(&type_module); + assert!(res.is_ok(), "{}", res.err().unwrap()); +} + +#[no_mangle] +pub extern "C" fn g_io_module_unload(module_ptr: *mut gio::ffi::GIOModule) { + let type_module = unsafe { glib::TypeModule::from_glib_borrow(module_ptr as *mut _) }; + let res = unregister(&type_module); + debug_assert!(res.is_ok(), "{}", res.err().unwrap()); +} + +pub fn register(type_module: &glib::TypeModule) -> Result<(), glib::Error> { + // register module types + if !vfs::imp::MyVfs::on_implementation_load(type_module) { + Err(glib::Error::new( + IOErrorEnum::Failed, + "failed to register module type MyVfs", + ))?; + } + if !file::imp::MyFile::on_implementation_load(type_module) { + Err(glib::Error::new( + IOErrorEnum::Failed, + "failed to register module type MyFile", + ))?; + } + if !file_enumerator::imp::MyFileEnumerator::on_implementation_load(type_module) { + Err(glib::Error::new( + IOErrorEnum::Failed, + "failed to register module type MyFileEnumerator", + ))?; + } + if !file_monitor::imp::MyFileMonitor::on_implementation_load(type_module) { + Err(glib::Error::new( + IOErrorEnum::Failed, + "failed to register module type MyFileMonitor", + ))?; + } + + if IOExtensionPoint::lookup(VFS_EXTENSION_POINT_NAME).is_none() { + let _ = IOExtensionPoint::builder(VFS_EXTENSION_POINT_NAME).build(); + } + IOExtensionPoint::implement( + VFS_EXTENSION_POINT_NAME, + vfs::MyVfs::static_type(), + SCHEME, + 20, + ) + .ok_or(glib::Error::new( + IOErrorEnum::Failed, + "failed to register vfs extension point", + ))?; + + g_debug!("MyVfs", "myvfs registered !!!"); + Ok(()) +} + +pub fn unregister(type_module: &glib::TypeModule) -> Result<(), glib::Error> { + // unregister module types + if !file_monitor::imp::MyFileMonitor::on_implementation_unload(type_module) { + Err(glib::Error::new( + IOErrorEnum::Failed, + "failed to unregister module type MyFileMonitor", + ))?; + } + if !file_enumerator::imp::MyFileEnumerator::on_implementation_unload(type_module) { + Err(glib::Error::new( + IOErrorEnum::Failed, + "failed to register module type MyFileEnumerator", + ))?; + } + if !file::imp::MyFile::on_implementation_unload(type_module) { + Err(glib::Error::new( + IOErrorEnum::Failed, + "failed to register module type MyFile", + ))?; + } + if !vfs::imp::MyVfs::on_implementation_unload(type_module) { + Err(glib::Error::new( + IOErrorEnum::Failed, + "failed to register module type MyVfs", + ))?; + } + + g_debug!("MyVfs", "myvfs unregistered !!!"); + Ok(()) +} + +pub const SCHEME: &str = "myvfs"; + +pub const MYVFS_ROOT: &str = "MYVFS_ROOT"; + +pub const DEFAULT_MYVFS_ROOT: &str = "/tmp/myvfs"; + +pub fn resolve_virtual_path>(local_path: T) -> String { + let local_root = glib::getenv(MYVFS_ROOT) + .and_then(|os| os.into_string().ok()) + .unwrap_or(DEFAULT_MYVFS_ROOT.to_string()); + g_debug!( + "MyVfs", + "resolve_virtual_path({},{})", + local_root, + local_path.as_ref() + ); + local_path + .as_ref() + .strip_prefix(&local_root) + .unwrap_or(local_path.as_ref()) + .to_string() +} + +pub fn resolve_local_path>(virtual_path: T) -> String { + let local_root = glib::getenv(MYVFS_ROOT) + .and_then(|os| os.into_string().ok()) + .unwrap_or(DEFAULT_MYVFS_ROOT.to_string()); + g_debug!( + "MyVfs", + "resolve_local_path({},{})", + local_root, + virtual_path.as_ref() + ); + format!("{}/{}", local_root, virtual_path.as_ref()).replace("//", "/") +} + +pub fn update_file_info(info: &gio::FileInfo) { + if let Some(v) = info.attribute_as_string(gio::FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET) { + info.set_attribute_string( + gio::FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET, + &format!("{}://{}", SCHEME, resolve_virtual_path(v)), + ); + } +} diff --git a/examples/gio_vfs/vfs.rs b/examples/gio_vfs/vfs.rs new file mode 100644 index 000000000000..331924cf5d24 --- /dev/null +++ b/examples/gio_vfs/vfs.rs @@ -0,0 +1,87 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use gio::{prelude::*, subclass::prelude::*, File, Vfs}; +use glib::g_debug; + +use crate::SCHEME; + +// Define `MyVfs` as a subclass of `Vfs`. +pub mod imp { + use std::path::PathBuf; + + use glib::object::Cast; + + use crate::{file::MyFile, resolve_local_path}; + + use super::*; + + #[derive(Default, Debug)] + pub struct MyVfs; + + #[glib::object_subclass] + #[object_subclass_dynamic(lazy_registration = true)] + impl ObjectSubclass for MyVfs { + const NAME: &'static str = "MyVfs"; + type Type = super::MyVfs; + type ParentType = Vfs; + } + + impl ObjectImpl for MyVfs {} + + impl VfsImpl for MyVfs { + fn is_active(&self) -> bool { + true + } + + fn get_file_for_path(&self, path: impl AsRef) -> File { + g_debug!( + "MyVfs", + "MyVfs::get_file_for_path({:?},{:?})", + self, + path.as_ref() + ); + Vfs::local().file_for_path(path) + } + + fn get_file_for_uri(&self, uri: &str) -> File { + g_debug!("MyVfs", "MyVfs::get_file_for_uri({:?},{})", self, uri); + if let Some(path) = uri.strip_prefix(&format!("{}://", SCHEME)) { + MyFile::new( + PathBuf::from(path), + Vfs::local().file_for_path(resolve_local_path(path)), + ) + .upcast() + } else { + Vfs::local().file_for_uri(uri) + } + } + + fn get_supported_uri_schemes(&self) -> Vec { + g_debug!("MyVfs", "MyVfs::get_supported_uri_schemes({:?})", self); + let mut schemes: Vec = Vfs::local() + .supported_uri_schemes() + .iter() + .map(|scheme| scheme.to_string()) + .collect(); + schemes.push(SCHEME.to_owned()); + schemes + } + + fn parse_name(&self, parse_name: &str) -> File { + g_debug!("MyVfs", "MyVfs::parse_name({:?},{})", self, parse_name); + if let Some(path) = parse_name.strip_prefix(&format!("{}://", SCHEME)) { + MyFile::new( + PathBuf::from(path), + Vfs::local().parse_name(&resolve_local_path(path)), + ) + .upcast() + } else { + Vfs::local().parse_name(parse_name) + } + } + } +} + +glib::wrapper! { + pub struct MyVfs(ObjectSubclass) @extends Vfs; +}