use glib::{prelude::*, subclass::prelude::*, translate::*};
use crate::{File, FileMonitor, FileMonitorEvent, ffi};
pub trait FileMonitorImpl: ObjectImpl + ObjectSubclass<Type: IsA<FileMonitor>> {
fn changed(&self, file: &File, other_file: Option<&File>, event_type: FileMonitorEvent) {
self.parent_changed(file, other_file, event_type)
}
fn cancel(&self) {
self.parent_cancel()
}
}
pub trait FileMonitorImplExt: FileMonitorImpl {
fn parent_changed(&self, file: &File, other_file: Option<&File>, event_type: FileMonitorEvent) {
unsafe {
let data = Self::type_data();
let parent_class = data.as_ref().parent_class() as *const ffi::GFileMonitorClass;
if let Some(f) = (*parent_class).changed {
f(
self.obj().unsafe_cast_ref::<FileMonitor>().to_glib_none().0,
file.to_glib_none().0,
other_file.to_glib_none().0,
event_type.into_glib(),
);
}
}
}
fn parent_cancel(&self) {
unsafe {
let data = Self::type_data();
let parent_class = data.as_ref().parent_class() as *const ffi::GFileMonitorClass;
let f = (*parent_class)
.cancel
.expect("No parent class implementation for \"cancel\"");
let _ = f(self.obj().unsafe_cast_ref::<FileMonitor>().to_glib_none().0);
}
}
}
impl<T: FileMonitorImpl> FileMonitorImplExt for T {}
unsafe impl<T: FileMonitorImpl> IsSubclassable<T> for FileMonitor {
fn class_init(class: &mut ::glib::Class<Self>) {
Self::parent_class_init::<T>(class);
let klass = class.as_mut();
klass.changed = Some(changed::<T>);
klass.cancel = Some(cancel::<T>);
}
}
unsafe extern "C" fn changed<T: FileMonitorImpl>(
monitor: *mut ffi::GFileMonitor,
file: *mut ffi::GFile,
other_file: *mut ffi::GFile,
event_type: ffi::GFileMonitorEvent,
) {
unsafe {
let instance = &*(monitor as *mut T::Instance);
let imp = instance.imp();
let other_file = Option::<File>::from_glib_none(other_file);
imp.changed(
&from_glib_borrow(file),
other_file.as_ref(),
from_glib(event_type),
);
}
}
unsafe extern "C" fn cancel<T: FileMonitorImpl>(
monitor: *mut ffi::GFileMonitor,
) -> glib::ffi::gboolean {
unsafe {
let instance = &*(monitor as *mut T::Instance);
let imp = instance.imp();
imp.cancel();
true.into_glib()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::prelude::*;
mod imp {
use super::*;
#[derive(Default)]
pub struct MyFileMonitor;
#[glib::object_subclass]
impl ObjectSubclass for MyFileMonitor {
const NAME: &'static str = "MyFileMonitor";
type Type = super::MyFileMonitor;
type ParentType = FileMonitor;
}
impl ObjectImpl for MyFileMonitor {}
impl FileMonitorImpl for MyFileMonitor {
fn cancel(&self) {}
}
#[derive(Default)]
pub struct MyCustomFileMonitor;
#[glib::object_subclass]
impl ObjectSubclass for MyCustomFileMonitor {
const NAME: &'static str = "MyCustomFileMonitor";
type Type = super::MyCustomFileMonitor;
type ParentType = super::MyFileMonitor;
}
impl ObjectImpl for MyCustomFileMonitor {}
impl FileMonitorImpl for MyCustomFileMonitor {}
impl MyFileMonitorImpl for MyCustomFileMonitor {}
}
glib::wrapper! {
pub struct MyFileMonitor(ObjectSubclass<imp::MyFileMonitor>) @extends FileMonitor;
}
pub trait MyFileMonitorImpl:
ObjectImpl + ObjectSubclass<Type: IsA<MyFileMonitor> + IsA<FileMonitor>>
{
}
unsafe impl<T: MyFileMonitorImpl + FileMonitorImpl> IsSubclassable<T> for MyFileMonitor {}
glib::wrapper! {
pub struct MyCustomFileMonitor(ObjectSubclass<imp::MyCustomFileMonitor>) @extends MyFileMonitor, FileMonitor;
}
#[test]
fn file_monitor_changed() {
let _ = glib::MainContext::new().with_thread_default(|| {
let my_custom_file_monitor = glib::Object::new::<MyCustomFileMonitor>();
let rx = {
let (tx, rx) = async_channel::bounded(1);
my_custom_file_monitor.connect_changed(move |_, file, other_file, event_type| {
let res = glib::MainContext::ref_thread_default().block_on(tx.send((
file.uri(),
other_file.map(File::uri),
event_type,
)));
assert!(res.is_ok(), "{}", res.err().unwrap());
});
rx
};
my_custom_file_monitor.emit_event(
&File::for_uri("child"),
None::<&File>,
FileMonitorEvent::Created,
);
let res = glib::MainContext::ref_thread_default().block_on(rx.recv());
assert!(res.is_ok(), "{}", res.err().unwrap());
let event = res.unwrap();
let my_file_monitor = glib::Object::new::<MyFileMonitor>();
let expected_rx = {
let (tx, rx) = async_channel::bounded(1);
my_file_monitor.connect_changed(move |_, file, other_file, event_type| {
let res = glib::MainContext::ref_thread_default().block_on(tx.send((
file.uri(),
other_file.map(File::uri),
event_type,
)));
assert!(res.is_ok(), "{}", res.err().unwrap());
});
rx
};
my_file_monitor.emit_event(
&File::for_uri("child"),
None::<&File>,
FileMonitorEvent::Created,
);
let res = glib::MainContext::ref_thread_default().block_on(expected_rx.recv());
assert!(res.is_ok(), "{}", res.err().unwrap());
let expected_event = res.unwrap();
assert_eq!(event, expected_event);
});
}
#[test]
fn file_monitor_cancel() {
let _ = glib::MainContext::new().with_thread_default(|| {
let my_custom_file_monitor = glib::Object::new::<MyCustomFileMonitor>();
let rx = {
let (tx, rx) = async_channel::bounded(1);
my_custom_file_monitor.connect_cancelled_notify(move |_| {
let res = glib::MainContext::ref_thread_default().block_on(tx.send(true));
assert!(res.is_ok(), "{}", res.err().unwrap());
});
rx
};
let cancelled = my_custom_file_monitor.cancel();
let res = glib::MainContext::ref_thread_default().block_on(rx.recv());
assert!(res.is_ok(), "{}", res.err().unwrap());
let notified = res.unwrap();
assert_eq!(cancelled, notified);
let my_file_monitor = glib::Object::new::<MyFileMonitor>();
let expected_rx = {
let (tx, rx) = async_channel::bounded(1);
my_file_monitor.connect_cancelled_notify(move |_| {
let res = glib::MainContext::ref_thread_default().block_on(tx.send(true));
assert!(res.is_ok(), "{}", res.err().unwrap());
});
rx
};
let expected_cancelled = my_file_monitor.cancel();
let res = glib::MainContext::ref_thread_default().block_on(expected_rx.recv());
assert!(res.is_ok(), "{}", res.err().unwrap());
let expected_notified = res.unwrap();
assert_eq!(expected_cancelled, expected_notified);
assert_eq!(cancelled, expected_cancelled);
assert_eq!(notified, expected_notified);
});
}
}