cacao/appkit/filesystem/manager.rs

90 lines
3.2 KiB
Rust

//! 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<Id<Object>>
}
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<Url, Box<dyn Error>> {
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<dyn Error>> {
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(())
}
}