diff --git a/Cargo.toml b/Cargo.toml index 0e5ca9b..e0a3291 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "fsevent" version = "2.2.0" -authors = [ "Pierre Baillet " ] +authors = ["Pierre Baillet "] description = "Rust bindings to the fsevent-sys macOS API for file changes notifications" license = "MIT" repository = "https://github.com/octplane/fsevent-rust" @@ -10,9 +10,17 @@ edition = "2018" [dependencies] bitflags = "1" -fsevent-sys = "5.1.0" -# fsevent-sys = { path = "fsevent-sys" } -core-foundation = "0.10.1" +objc2-core-foundation = { version = "0.3.2", default-features = false, features = [ + "std", + "CFString", + "CFRunLoop", + "CFArray", + "CFDate", +] } +objc2-core-services = { version = "0.3.2", default-features = false, features = [ + "std", + "FSEvents", +] } [dev-dependencies] tempfile = "3" diff --git a/fsevent-sys/src/lib.rs b/fsevent-sys/src/lib.rs index ae10a35..41c5cf3 100644 --- a/fsevent-sys/src/lib.rs +++ b/fsevent-sys/src/lib.rs @@ -1,4 +1,5 @@ #![cfg(target_os = "macos")] +#![deprecated = "deprecated in favour of the `objc2-core-services` crate"] mod fsevent; pub use fsevent::*; diff --git a/src/lib.rs b/src/lib.rs index 4de85d5..bec6554 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,26 +7,41 @@ )] use bitflags::bitflags; -use core_foundation::{ - array::CFArray, - base::{kCFAllocatorDefault, TCFType}, - date::CFTimeInterval, - runloop::{ - kCFRunLoopDefaultMode, CFRunLoopGetCurrent, CFRunLoopRef, CFRunLoopRun, CFRunLoopStop, - }, - string::CFString, +use objc2_core_foundation::{ + kCFAllocatorDefault, kCFRunLoopDefaultMode, CFArray, CFRetained, CFRunLoop, CFString, + CFTimeInterval, +}; +#[allow(deprecated)] +use objc2_core_services::FSEventStreamScheduleWithRunLoop; +use objc2_core_services::{ + kFSEventStreamCreateFlagFileEvents, kFSEventStreamCreateFlagNoDefer, + kFSEventStreamEventFlagEventIdsWrapped, kFSEventStreamEventFlagHistoryDone, + kFSEventStreamEventFlagItemChangeOwner, kFSEventStreamEventFlagItemCloned, + kFSEventStreamEventFlagItemCreated, kFSEventStreamEventFlagItemFinderInfoMod, + kFSEventStreamEventFlagItemInodeMetaMod, kFSEventStreamEventFlagItemIsDir, + kFSEventStreamEventFlagItemIsFile, kFSEventStreamEventFlagItemIsHardlink, + kFSEventStreamEventFlagItemIsLastHardlink, kFSEventStreamEventFlagItemIsSymlink, + kFSEventStreamEventFlagItemModified, kFSEventStreamEventFlagItemRemoved, + kFSEventStreamEventFlagItemRenamed, kFSEventStreamEventFlagItemXattrMod, + kFSEventStreamEventFlagKernelDropped, kFSEventStreamEventFlagMount, + kFSEventStreamEventFlagMustScanSubDirs, kFSEventStreamEventFlagNone, + kFSEventStreamEventFlagOwnEvent, kFSEventStreamEventFlagRootChanged, + kFSEventStreamEventFlagUnmount, kFSEventStreamEventFlagUserDropped, + kFSEventStreamEventIdSinceNow, ConstFSEventStreamRef, FSEventStreamContext, + FSEventStreamCreate, FSEventStreamCreateFlags, FSEventStreamEventFlags, FSEventStreamEventId, + FSEventStreamFlushSync, FSEventStreamStart, FSEventStreamStop, }; -use fsevent_sys as fs; use std::{ ffi::CStr, fmt::{Display, Formatter}, os::raw::c_void, + ptr::NonNull, slice, sync::mpsc::Sender, }; // Helper to send the runloop from an observer thread. -struct CFRunLoopSendWrapper(CFRunLoopRef); +struct CFRunLoopSendWrapper(CFRetained); // Safety: According to the Apple documentation, it is safe to send CFRef types across threads. // @@ -35,10 +50,10 @@ unsafe impl Send for CFRunLoopSendWrapper {} pub struct FsEvent { paths: Vec, - since_when: fs::FSEventStreamEventId, + since_when: FSEventStreamEventId, latency: CFTimeInterval, - flags: fs::FSEventStreamCreateFlags, - runloop: Option, + flags: FSEventStreamCreateFlags, + runloop: Option>, } #[derive(Debug)] @@ -53,30 +68,30 @@ pub struct Event { bitflags! { #[repr(C)] pub struct StreamFlags: u32 { - const NONE = 0x00000000; - const MUST_SCAN_SUBDIRS = 0x00000001; - const USER_DROPPED = 0x00000002; - const KERNEL_DROPPED = 0x00000004; - const IDS_WRAPPED = 0x00000008; - const HISTORY_DONE = 0x00000010; - const ROOT_CHANGED = 0x00000020; - const MOUNT = 0x00000040; - const UNMOUNT = 0x00000080; - const ITEM_CREATED = 0x00000100; - const ITEM_REMOVED = 0x00000200; - const INODE_META_MOD = 0x00000400; - const ITEM_RENAMED = 0x00000800; - const ITEM_MODIFIED = 0x00001000; - const FINDER_INFO_MOD = 0x00002000; - const ITEM_CHANGE_OWNER = 0x00004000; - const ITEM_XATTR_MOD = 0x00008000; - const IS_FILE = 0x00010000; - const IS_DIR = 0x00020000; - const IS_SYMLINK = 0x00040000; - const OWN_EVENT = 0x00080000; - const IS_HARDLINK = 0x00100000; - const IS_LAST_HARDLINK = 0x00200000; - const ITEM_CLONED = 0x400000; + const NONE = kFSEventStreamEventFlagNone; + const MUST_SCAN_SUBDIRS = kFSEventStreamEventFlagMustScanSubDirs; + const USER_DROPPED = kFSEventStreamEventFlagUserDropped; + const KERNEL_DROPPED = kFSEventStreamEventFlagKernelDropped; + const IDS_WRAPPED = kFSEventStreamEventFlagEventIdsWrapped; + const HISTORY_DONE = kFSEventStreamEventFlagHistoryDone; + const ROOT_CHANGED = kFSEventStreamEventFlagRootChanged; + const MOUNT = kFSEventStreamEventFlagMount; + const UNMOUNT = kFSEventStreamEventFlagUnmount; + const ITEM_CREATED = kFSEventStreamEventFlagItemCreated; + const ITEM_REMOVED = kFSEventStreamEventFlagItemRemoved; + const INODE_META_MOD = kFSEventStreamEventFlagItemInodeMetaMod; + const ITEM_RENAMED = kFSEventStreamEventFlagItemRenamed; + const ITEM_MODIFIED = kFSEventStreamEventFlagItemModified; + const FINDER_INFO_MOD = kFSEventStreamEventFlagItemFinderInfoMod; + const ITEM_CHANGE_OWNER = kFSEventStreamEventFlagItemChangeOwner; + const ITEM_XATTR_MOD = kFSEventStreamEventFlagItemXattrMod; + const IS_FILE = kFSEventStreamEventFlagItemIsFile; + const IS_DIR = kFSEventStreamEventFlagItemIsDir; + const IS_SYMLINK = kFSEventStreamEventFlagItemIsSymlink; + const OWN_EVENT = kFSEventStreamEventFlagOwnEvent; + const IS_HARDLINK = kFSEventStreamEventFlagItemIsHardlink; + const IS_LAST_HARDLINK = kFSEventStreamEventFlagItemIsLastHardlink; + const ITEM_CLONED = kFSEventStreamEventFlagItemCloned; } } @@ -155,14 +170,14 @@ impl Display for StreamFlags { } } -fn default_stream_context(event_sender: *const Sender) -> fs::FSEventStreamContext { +fn default_stream_context(event_sender: *const Sender) -> FSEventStreamContext { let ptr = event_sender as *mut c_void; - fs::FSEventStreamContext { + FSEventStreamContext { version: 0, info: ptr, retain: None, release: None, - copy_description: None, + copyDescription: None, } } @@ -189,9 +204,9 @@ impl FsEvent { pub fn new(paths: Vec) -> Self { Self { paths, - since_when: fs::kFSEventStreamEventIdSinceNow, + since_when: kFSEventStreamEventIdSinceNow, latency: 0.0, - flags: fs::kFSEventStreamCreateFlagFileEvents | fs::kFSEventStreamCreateFlagNoDefer, + flags: kFSEventStreamCreateFlagFileEvents | kFSEventStreamCreateFlagNoDefer, runloop: None, } } @@ -202,48 +217,49 @@ impl FsEvent { Ok(()) } - fn build_native_paths(&self) -> CFArray { - let paths: Vec<_> = self.paths.iter().map(|x| CFString::new(x)).collect(); - CFArray::from_CFTypes(&paths) + fn build_native_paths(&self) -> CFRetained> { + let paths: Vec<_> = self.paths.iter().map(|x| CFString::from_str(x)).collect(); + CFArray::from_retained_objects(&paths) } fn internal_observe( - since_when: fs::FSEventStreamEventId, + since_when: FSEventStreamEventId, latency: CFTimeInterval, - flags: fs::FSEventStreamCreateFlags, - paths: CFArray, + flags: FSEventStreamCreateFlags, + paths: &CFArray, event_sender: Sender, runloop_sender: Option>, ) -> Result<()> { let stream_context = default_stream_context(&event_sender); unsafe { - let stream = fs::FSEventStreamCreate( + let stream = FSEventStreamCreate( kCFAllocatorDefault, - callback, - &stream_context, - paths.as_concrete_TypeRef() as _, + Some(callback), + &stream_context as *const _ as *mut _, + paths.as_opaque(), since_when, latency, flags, ); if let Some(ret_tx) = runloop_sender { - let runloop = CFRunLoopSendWrapper(CFRunLoopGetCurrent()); + let runloop = CFRunLoopSendWrapper(CFRunLoop::current().unwrap()); ret_tx.send(runloop).expect("unabe to send CFRunLoopRef"); } - fs::FSEventStreamScheduleWithRunLoop( + #[allow(deprecated)] + FSEventStreamScheduleWithRunLoop( stream, - CFRunLoopGetCurrent(), - kCFRunLoopDefaultMode, + &CFRunLoop::current().unwrap(), + kCFRunLoopDefaultMode.unwrap(), ); - fs::FSEventStreamStart(stream); - CFRunLoopRun(); + FSEventStreamStart(stream); + CFRunLoop::run(); - fs::FSEventStreamFlushSync(stream); - fs::FSEventStreamStop(stream); + FSEventStreamFlushSync(stream); + FSEventStreamStop(stream); } Ok(()) @@ -255,7 +271,7 @@ impl FsEvent { self.since_when, self.latency, self.flags, - native_paths, + &native_paths, event_sender, None, ) @@ -266,7 +282,7 @@ impl FsEvent { let (ret_tx, ret_rx) = std::sync::mpsc::channel(); let native_paths = self.build_native_paths(); - struct CFMutableArraySendWrapper(CFArray); + struct CFMutableArraySendWrapper(CFRetained>); // Safety // - See comment on `CFRunLoopSendWrapper @@ -282,7 +298,7 @@ impl FsEvent { since_when, latency, flags, - paths.0, + &paths.0, event_sender, Some(ret_tx), ) @@ -296,24 +312,23 @@ impl FsEvent { // Shut down the event stream. pub fn shutdown_observe(&mut self) { if let Some(runloop) = self.runloop.take() { - unsafe { - CFRunLoopStop(runloop); - } + runloop.stop(); } } } -extern "C" fn callback( - _stream_ref: fs::FSEventStreamRef, +unsafe extern "C-unwind" fn callback( + _stream_ref: ConstFSEventStreamRef, info: *mut c_void, - num_events: usize, // size_t numEvents - event_paths: *mut c_void, // void *eventPaths - event_flags: *const fs::FSEventStreamEventFlags, // const FSEventStreamEventFlags eventFlags[] - event_ids: *const fs::FSEventStreamEventId, // const FSEventStreamEventId eventIds[] + num_events: usize, // size_t numEvents + event_paths: NonNull, // void *eventPaths + event_flags: NonNull, // const FSEventStreamEventFlags eventFlags[] + event_ids: NonNull, // const FSEventStreamEventId eventIds[] ) { - let event_paths = unsafe { slice::from_raw_parts(event_paths as *const *const i8, num_events) }; - let event_flags = unsafe { slice::from_raw_parts(event_flags, num_events) }; - let event_ids = unsafe { slice::from_raw_parts(event_ids, num_events) }; + let event_paths = + unsafe { slice::from_raw_parts(event_paths.as_ptr() as *const *const i8, num_events) }; + let event_flags = unsafe { slice::from_raw_parts(event_flags.as_ptr(), num_events) }; + let event_ids = unsafe { slice::from_raw_parts(event_ids.as_ptr(), num_events) }; let sender = unsafe { (info as *mut Sender) .as_mut() diff --git a/tests/fsevent.rs b/tests/fsevent.rs index 3fb0620..de61c22 100644 --- a/tests/fsevent.rs +++ b/tests/fsevent.rs @@ -1,7 +1,7 @@ #![cfg(target_os = "macos")] -use core_foundation::runloop::{CFRunLoopGetCurrent, CFRunLoopRef, CFRunLoopStop}; use fsevent::*; +use objc2_core_foundation::{CFRetained, CFRunLoop}; use std::{ fs, fs::{read_link, OpenOptions}, @@ -13,7 +13,7 @@ use std::{ }; // Helper to send the runloop from an observer thread. -struct CFRunLoopSendWrapper(CFRunLoopRef); +struct CFRunLoopSendWrapper(CFRetained); // Safety: According to the Apple documentation, it is safe to send CFRef types across threads. // @@ -121,7 +121,7 @@ fn internal_observe_folder(run_async: bool) { let (tx, rx) = std::sync::mpsc::channel(); let dst_clone = dst.clone(); let observe_thread = thread::spawn(move || { - let runloop = unsafe { CFRunLoopGetCurrent() }; + let runloop = CFRunLoop::current().unwrap(); tx.send(CFRunLoopSendWrapper(runloop)).unwrap(); let mut fsevent = fsevent::FsEvent::new(vec![]); @@ -163,9 +163,7 @@ fn internal_observe_folder(run_async: bool) { ); if let Some((runloop, thread)) = runloop_and_thread { - unsafe { - CFRunLoopStop(runloop); - } + runloop.stop(); thread.join().unwrap(); } else { @@ -204,7 +202,7 @@ fn internal_validate_watch_single_file(run_async: bool) { let dir_path_clone = dir_path.clone(); let observe_thread = thread::spawn(move || { - let runloop = unsafe { CFRunLoopGetCurrent() }; + let runloop = CFRunLoop::current().unwrap(); tx.send(CFRunLoopSendWrapper(runloop)).unwrap(); let mut fsevent = fsevent::FsEvent::new(vec![]); @@ -257,9 +255,7 @@ fn internal_validate_watch_single_file(run_async: bool) { ); if let Some((runloop, observe_thread)) = runloop_and_thread { - unsafe { - CFRunLoopStop(runloop); - } + runloop.stop(); observe_thread.join().unwrap(); } else {