diff --git a/Cargo.toml b/Cargo.toml index 1413727..26f9144 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,10 +32,18 @@ tempfile = "3.8.0" [target.'cfg(target_os = "macos")'.dependencies] -objc = "0.2.7" +objc2 = "0.5.1" +objc2-foundation = { version = "0.2.0", features = [ + "NSError", + "NSFileManager", + "NSString", + "NSURL", +] } [target.'cfg(all(unix, not(target_os = "macos"), not(target_os = "ios"), not(target_os = "android")))'.dependencies] -chrono = { version = "0.4.31", optional = true, default-features = false, features = ["clock"] } +chrono = { version = "0.4.31", optional = true, default-features = false, features = [ + "clock", +] } libc = "0.2.149" scopeguard = "1.2.0" url = "2.4.1" diff --git a/src/macos.rs b/src/macos.rs index ace1e8c..43c3009 100644 --- a/src/macos.rs +++ b/src/macos.rs @@ -1,27 +1,10 @@ use std::{ffi::OsString, path::PathBuf, process::Command}; -use log::{trace, warn}; -use objc::{ - class, msg_send, - runtime::{Object, BOOL, NO}, - sel, sel_impl, -}; +use log::trace; +use objc2_foundation::{NSFileManager, NSString, NSURL}; use crate::{into_unknown, Error, TrashContext}; -#[link(name = "Foundation", kind = "framework")] -extern "C" { - // Using an empty scope to just link against the foundation framework, - // to find the NSFileManager, but we don't need anything else from it. -} - -#[allow(non_camel_case_types)] -type id = *mut Object; -#[allow(non_upper_case_globals)] -const nil: id = std::ptr::null_mut(); -#[allow(non_upper_case_globals)] -const NSUTF8StringEncoding: usize = 4; - #[derive(Copy, Clone, Debug)] pub enum DeleteMethod { /// Use an `osascript`, asking the Finder application to delete the files. @@ -89,49 +72,21 @@ impl TrashContext { fn delete_using_file_mgr(full_paths: Vec) -> Result<(), Error> { trace!("Starting delete_using_file_mgr"); - let url_cls = class!(NSURL); - let file_mgr_cls = class!(NSFileManager); - let file_mgr: id = unsafe { msg_send![file_mgr_cls, defaultManager] }; + let file_mgr = unsafe { NSFileManager::defaultManager() }; for path in full_paths { - let string = to_ns_string(&path); + let string = NSString::from_str(&path); + trace!("Starting fileURLWithPath"); - let url: id = unsafe { msg_send![url_cls, fileURLWithPath:string.ptr] }; - if url == nil { - return Err(Error::Unknown { - description: format!("Failed to convert a path to an NSURL. Path: '{path}'"), - }); - } + let url = unsafe { NSURL::fileURLWithPath(&string) }; trace!("Finished fileURLWithPath"); - // WARNING: I don't know why but if we try to call release on the url, it sometimes - // crashes with SIGSEGV, so we instead don't try to release the url - // let url = OwnedObject { ptr: url }; - let mut error: id = nil; + trace!("Calling trashItemAtURL"); - let success: BOOL = unsafe { - msg_send![ - file_mgr, - trashItemAtURL:url - resultingItemURL:nil - error:(&mut error as *mut id) - ] - }; + let res = unsafe { file_mgr.trashItemAtURL_resultingItemURL_error(&url, None) }; trace!("Finished trashItemAtURL"); - if success == NO { - trace!("success was NO"); - if error == nil { - return Err(Error::Unknown { - description: format!( - "While deleting '{path}', `trashItemAtURL` returned with failure but no error was specified.", - ), - }); - } - let code: isize = unsafe { msg_send![error, code] }; - let domain: id = unsafe { msg_send![error, domain] }; - let domain = unsafe { ns_string_to_rust(domain)? }; + + if let Err(err) = res { return Err(Error::Unknown { - description: format!( - "While deleting '{path}', `trashItemAtURL` failed, code: {code}, domain: {domain}", - ), + description: format!("While deleting '{path}', `trashItemAtURL` failed: {err}"), }); } } @@ -180,52 +135,6 @@ fn to_string>(str_in: T) -> Result { } } -/// Uses the Drop trait to `release` the object held by `ptr`. -#[repr(transparent)] -struct OwnedObject { - pub ptr: id, -} -impl Drop for OwnedObject { - fn drop(&mut self) { - #[allow(clippy::let_unit_value)] - { - let () = unsafe { msg_send![self.ptr, release] }; - } - } -} - -fn to_ns_string(s: &str) -> OwnedObject { - trace!("Called to_ns_string on '{}'", s); - let utf8 = s.as_bytes(); - let string_cls = class!(NSString); - let alloced_string: id = unsafe { msg_send![string_cls, alloc] }; - let mut string: id = unsafe { - msg_send![ - alloced_string, - initWithBytes:utf8.as_ptr() - length:utf8.len() - encoding:NSUTF8StringEncoding - ] - }; - if string == nil { - warn!("initWithBytes returned nil when trying to convert a rust string to an NSString"); - string = unsafe { msg_send![alloced_string, init] }; - } - OwnedObject { ptr: string } -} - -/// Safety: `string` is assumed to be a pointer to an NSString -unsafe fn ns_string_to_rust(string: id) -> Result { - if string == nil { - return Ok(String::new()); - } - let utf8_bytes: *const u8 = msg_send![string, UTF8String]; - let utf8_len: usize = msg_send![string, lengthOfBytesUsingEncoding: NSUTF8StringEncoding]; - let str_slice = std::slice::from_raw_parts(utf8_bytes, utf8_len); - let rust_str = std::str::from_utf8(str_slice).map_err(into_unknown)?; - Ok(rust_str.to_owned()) -} - #[cfg(test)] mod tests { use crate::{