//! A wrapper for `NSFileManager`, which is necessary for macOS/iOS (the sandbox makes things //! tricky, and this transparently handles it for you). use std::error::Error; use std::sync::RwLock; use objc_id::Id; use objc::runtime::{BOOL, Object}; use objc::{class, msg_send, sel, sel_impl}; use url::Url; use crate::foundation::{id, nil, NO, NSString, NSUInteger}; use crate::error::AppKitError; use crate::filesystem::enums::{SearchPathDirectory, SearchPathDomainMask}; pub struct FileManager { pub manager: RwLock> } impl Default for FileManager { /// Returns a default file manager, which maps to the default system file manager. For common /// and simple tasks, with no callbacks, you might want this. fn default() -> Self { FileManager { manager: RwLock::new(unsafe { let manager: id = msg_send![class!(NSFileManager), defaultManager]; Id::from_ptr(manager) }) } } } impl FileManager { /// Returns a new FileManager that opts in to delegate methods. pub fn new() -> Self { FileManager { manager: RwLock::new(unsafe { let manager: id = msg_send![class!(NSFileManager), new]; Id::from_ptr(manager) }) } } /// Given a directory/domain combination, will attempt to get the directory that matches. /// Returns a PathBuf that wraps the given location. If there's an error on the Objective-C /// side, we attempt to catch it and bubble it up. pub fn get_directory(&self, directory: SearchPathDirectory, in_domain: SearchPathDomainMask) -> Result> { let dir: NSUInteger = directory.into(); let mask: NSUInteger = in_domain.into(); let directory = unsafe { let manager = self.manager.read().unwrap(); let dir: id = msg_send![&**manager, URLForDirectory:dir inDomain:mask appropriateForURL:nil create:NO error:nil]; NSString::wrap(msg_send![dir, absoluteString]).to_str() }; Url::parse(directory).map_err(|e| e.into()) } /// Given two paths, moves file (`from`) to the location specified in `to`. This can result in /// an error on the Objective-C side, which we attempt to handle and bubble up as a result if /// so. pub fn move_item(&self, from: Url, to: Url) -> Result<(), Box> { let from = NSString::new(from.as_str()); let to = NSString::new(to.as_str()); unsafe { let from_url: id = msg_send![class!(NSURL), URLWithString:from.into_inner()]; let to_url: id = msg_send![class!(NSURL), URLWithString:to.into_inner()]; // This should potentially be write(), but the backing class handles this logic // already, so... going to leave it as read. let manager = self.manager.read().unwrap(); let error: id = nil; let result: BOOL = msg_send![&**manager, moveItemAtURL:from_url toURL:to_url error:&error]; if result == NO { return Err(AppKitError::new(error).into()); } } Ok(()) } }